時折、ある処理中に多次元配列で得られた値を次の処理に渡すために input 要素に渡すことがあります。具体的にいうと入力画面から確認画面に POST されてきたデータを確認画面から完了画面に POST する際に引き回す時です。この際、よく次の様に input[typ=”hidden”] なタグを列挙します。
{{-- テンプレートエンジンがBladeの場合 --}}
<form action='/hoge/complete'>
<input type="hidden" name="name" value="{{ $request['name'] }}">
<input type="hidden" name="address" value="{{ $request['address'] }}">
{{-- ここに確認画面表示用のHTML色々 --}}
<button type='submit'>送信</button>
</form>
値を echo する方法はテンプレートによりまちまちですが、ロジックが変わることはありません。例では name, address の二つだけでしたが、このパラメータ数が 10, 20 と増えるにつれて直打ちによるべた書きは大変辛くなってきますし、コードも見難くなります。そこで列挙の元となるパラメータを配列で用意して、テンプレート内ではループで展開する方法の需要があります。
ループによる展開は元となる配列がシンプルであるほどシンプルに実装できます。例えば、上述の name, address といった一つのキーに一つの値が約束されているとすると次の様に実装できます。
{{-- テンプレートエンジンがBladeの場合 --}}
<form action='/hoge/complete'>
@foreach( $request as $name => $value)
<input type="hidden" name="{{ $name }}" value="{{ $value }}">
@endforeach
{{-- ここに確認画面表示用のHTML色々 --}}
<button type='submit'>送信</button>
</form>
チェックボックスなどでよくある一次元配列ぐらいまでなら次の様にテンプレートに処理を全て収めてもいいぐらいです。
{{-- テンプレートエンジンがBladeの場合 --}}
<form action='/hoge/complete'>
@foreach( $request as $name => $value)
@if( is_array($value) )
<input type="hidden" name="{{ $name }}[]" value="{{ $value }}">
@else
<input type="hidden" name="{{ $name }}" value="{{ $value }}">
@endif
@endforeach
{{-- ここに確認画面表示用のHTML色々 --}}
<button type='submit'>送信</button>
</form>
リクエストの構造がシンプル済むならば上述の様にあっさりループ処理が終わるのですが、そううまくいかない時もあります。例えば次の多次元配列かつ連想配列のリクエストが送られてくる場合、テンプレート内にループと分岐の処理を書くとコードが読み難く改修し難くなります。
const request = {
"name": "浜松太郎",
"checkbox1": [
"1",
"3",
"5"
],
"families": {
"0": {
"name": "静岡太郎",
"checkbox1": [
"2",
"5"
]
},
"3": {
"checkbox1": [
"6"
]
}
}
};
これに対応するため関数の実装が次です。次の関数は PHP 7 以降で動きます。特別な組み込み関数や機能を使っていないので関数宣言時の型や [] を array() にするなどすればもっと古いバージョンの PHP でも動くはずです。
/**
* 多次元配列を <input name="{$name}" value="{$value}"> で使いやすい形にする。
* <pre>
* input: [
* 'name' => '浜松太郎',
* 'checkbox1' => [0 => '1', 1 => '3', 2 => '5',],
* 'families' => [
* 0 => [
* 'name' => '静岡太郎',
* 'checkbox1' => [0 => '2', 1 => '5',],
* ],
* 3 => ['checkbox1' => [0 => '6',],],
* ],
* ]
* output: [
* 'name' => '浜松太郎',
* 'checkbox1[]' => [0 => '1', 1 => '3', 2 => '5',],
* 'families[0][name]' => '静岡太郎',
* 'families[0][checkbox1][]' => [0 => '2', 1 => '5',],
* 'families[3][checkbox1][]' => '6',
* ]
*
* </pre>
* @see https://qiita.com/puchida/items/e582488dc0a087c4bfcf この関数の再帰ロジックの参考元
* @param array|string|int|float $requests 呼出し時は配列。再帰時は string, int, float も
* @param array $ret 再帰で使う場所。呼出し時は無視で問題なし
* @param array $keys 再帰で使う場所。呼出し時は無視で問題なし
* @param string|null $key 再帰で使う場所。呼出し時は無視で問題なし
* @return array
*/
function request_arr_to_html_input_arr($requests, array $ret = [], array $keys = [], string $key = null): array
{
if ($key !== null) {
// 配列のキーに使う文字列を追加。一番最初に呼び出された時だけ $key === null
$keys[] = $key;
}
if (! is_array($requests)) {
// 貯まった文字列を結合してキーにして、配列に値を追加
// ex.
// <input type="text" name="hoge[fuga][]" value="foo"> とあったら、この時点で
// $keys === ['hoge', '[fuga]', '[]'] && $requests === 'foo'
// が成り立つ値が変数に格納されている
$inputName = implode('', $keys);
if (! isset($ret[$inputName])) {
// まだ入れる場所が未定義ならばとりあえずそのまま入れる
$ret[$inputName] = $requests;
} else {
// 重複による上書きを避けるために一次元配列にまとめる
if (! is_array($ret[$inputName])) {
// まだ配列化していないならば配列化
$ret[$inputName] = [$ret[$inputName], $requests];
} else {
// 配列化済みならば追加
$ret[$inputName][] = $requests;
}
}
return $ret;
}
// 配列が続いている場合、再帰でキーを連鎖させていく
if ($requests === [] || (array_keys($requests) === range(0, count($requests) - 1))) {
// 配列の添え字が 0, 1, 2,... と連番の場合
foreach ($requests as $i => $v) {
if (! is_array($v)) {
// 配列 input の末尾をループしているので [] をつける
$ret = request_arr_to_html_input_arr($v, $ret, $keys, '[]');
} else {
// まだ続きがあるので [${添え字}] をつける
$ret = request_arr_to_html_input_arr($v, $ret, $keys, '['.$i.']');
}
}
} else {
// 配列が連想配列の場合
if (empty($keys)) {
// $keys が空ならばトップレベルなので現在のキーをそのまま渡す
foreach ($requests as $k => $v) {
$ret = request_arr_to_html_input_arr($v, $ret, $keys, $k);
}
} else {
// $keys が空でないならばネスト中の連想配列なので現在のキーを [] でくくって渡す
foreach ($requests as $k => $v) {
$ret = request_arr_to_html_input_arr($v, $ret, $keys, '['.$k.']');
}
}
}
return $ret;
}
これを使うと次の様にできます。
// 実際にリクエストの引き回しで使う場合、ここに渡すリクエストの配列はバリデーション済みのものを推奨
$forInputs = request_arr_to_html_input_arr([
'name' => '浜松太郎',
'checkbox1' => [0 => '1', 1 => '3', 2 => '5',],
'families' => [
0 => [
'name' => '静岡太郎',
'checkbox1' => [0 => '2', 1 => '5',],
],
3 => ['checkbox1' => [0 => '6',],],
],
]);
// 実際はこの foreach 以下をテンプレート内に置く
foreach($forInputs as $name => $value){
if(is_string($value) || is_numeric($value) ){
echo "<input type=\"hidden\" name=\"{$name}\" value=\"{$value}\">\n";
}else if( is_array($value) ){
foreach($value as $v){
echo "<input type=\"hidden\" name=\"{$name}\" value=\"{$v}\">\n";
}
}
}
/***
以下 echo で出力される文字列
<input type="hidden" name="name" value="浜松太郎">
<input type="hidden" name="checkbox1[]" value="1">
<input type="hidden" name="checkbox1[]" value="3">
<input type="hidden" name="checkbox1[]" value="5">
<input type="hidden" name="families[0][name]" value="静岡太郎">
<input type="hidden" name="families[0][checkbox1][]" value="2">
<input type="hidden" name="families[0][checkbox1][]" value="5">
<input type="hidden" name="families[3][checkbox1][]" value="6">
***/
この様にすると大量のパラメータも比較的簡単にフォームリクエストで引き回せます。