Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

フォームデータを連想配列化

研究内容

ブラウザから送られてきたフォームデータを、連想配列に格納して便利に利用できるようにする 研究です。

詳細

HTMLのフォームで入力ボックスが2つあるとき、たとえば、次のようなデータがCGIプログラムに 送られてくるんでした。

input1=text1&input2=text2

このように、フォームが複数の入力ボックス、あるいはコンボボックス、テキストエリアなどから 出来ているとき、CGIプログラムにはそれらのデータがまとめて1つの文字列として送られてくるんでしたね。 ここまでは単に送られてきた文字列をそのまま画面に表示していただけでしたが、 CGIプログラム側ではその文字列を解析して、必要な文字列を取得しなければいけません。

例えば上の例で、送られてきたデータのうち、input1の内容(text1)を知りたいとしましょう。 上の文字列から、どうやって「text1」を抜き出したらいいんでしょう?

現状の知識ではサッパリですね。気が遠くなります。 でもそんなこと言ってられません。研究してマスターしましょう! 幸い、Perlという言語は、こういう文字列処理をするのにとても便利な機能があるみたいなんですよ。

結果

連想配列に格納しましょう

さて、送られてきたデータをどうやって抜き出すかということの前に、 どのように格納しておくかっていうことを考えましょう。 例えば、フォームに付けた名前と同じ、$input1、$input2という変数を用意して、 その中に入れてプログラムの中で使用するとか。そんな風に考えるのが普通かな、 と思うんですが、フォームデータの処理に関しては、頭のいい人が上手いやり方を 考えてくれています。連想配列に格納するっていうやり方です。 このやり方だと、HTML側でフォームにどんな名前を付けたのかとか、 そんなことを気にせずに、汎用的な「フォームデータ取得関数」を作ることが できます。

連想配列って?

連想配列っていうのはPerlにもともと用意されている、「名前=値」というペアを 格納するのに凄く便利な機能です。「名前=値」っていうと、 フォームデータの形そのまんまですね。ですから、フォームデータを 格納しておくのにもとても都合がいいんです。

連想配列の使い方

連想配列は % という記号を使います。連想配列名、これは何でもいいんですが、 その頭に % をつけると連想配列になります。 例えば、%formdata って書くと、formdataっていう名前の連想配列になります。 簡単ですねぇ。Perl偉大です。

で、使い方は、連想配列に値を格納するときは次のようにします。

$連想配列名{'名前'} = 値;

連想配列には % を使うと書きましたが、値を入れたり取り出したりするときは $ となるので注意しましょう。これ、なんでかというと、 「%連想配列名」は連想配列ですが、「$連想配列名{'名前'}」は 「連想配列名{'名前'}」という名前の変数だからです。 変数っていうのは説明しませんでしたが、値を入れておく入れ物で、頭に $ を付けるんです。 そんなわけで、連想配列名に {'名前'} をつけると変数になるので $ になる、ということを 覚えておきましょう。

値を取り出すには、次のようにします。

$連想配列名{'名前'}

例えば、 formdata という連想配列に input1 という名前で text1 という値を格納する場合…

$formdata{'input1'} = "text1";

値を取り出すには…

$formdata{'input1'}

そんなに難しくないですね!

あと、ここでは 'input1' というように直接名前を指定していましたが、 変数に入っている文字列を名前として連想配列にアクセスすることもできます。 例えば、上の $formdata{'input1'} は次のように書いてもOKです。

$name = 'input1';
$formdata{$name}
%ENV

さて、連想配列っていうのは、既にちょっとだけでてきています。 環境変数が入っている連想配列 %ENV ってやつです。 $ENV{'REQUEST_METHOD'} とか $ENV{'QUERY_STRING'} とか、そんな形で出てきていました。 環境変数はウェブサーバーが設定してくれるものだったので、 値を入れるっていうのはしていませんでしたが、値を利用するっていうのは 既にやってたわけですね。

連想配列にフォームデータを格納する、とは?

フォームデータはもともと「名前=値」でした。 これをそのまま連想配列に格納しましょう。 そうすると、次のような形で、値を取り出すことができるわけです。

$formdata{'HTMLでフォームに付けといた名前'}

送られてきたデータが「input1=text1&input2=text2」の場合だったら、

$formdata{'input1'} ("text1"が格納されている)
$formdata{'input2'} ("text2"が格納されている)

というようにデータにアクセスできるわけです。 これ、とても直感的で分かりやすくて便利ですね! フォームデータは連想配列に。これっきゃありません。

データを分離する関数 split

さて連想配列についてはなんとなく分かりました。 実際にはどんな処理をしたらいいんでしょう? まず、データが1個だけ送られてきた場合を考えます。 処理するデータはこうですね。

input1=text1

これを連想配列 %formdata に入れるためには、 例えば = の前の部分を $name、= の後ろの部分を $val という 変数になんとかして入れておいて、

$formdata{$name} = $val;

とすればいいですね。問題は、どうやって $name と $val にそれぞれ「input1」「text1」を 入れるのかっていうことです。

ここでまたPerlの便利な機能の登場です。 Perlには、「1つの文字列を、ある文字で区切って、複数の文字列に分ける」っていう機能があるんです。 参りました。最初からそんな機能が用意されているんなら、もしかして、簡単にできるんじゃないの? って感じです。

それは split 関数というものです。使い方は、次みたいな感じです。 split の処理結果は、配列というものに格納します。

@配列名 = split(区切り文字, 文字列);
配列って?

また新しい、配列っていう言葉が出てきました。 いろいろ勉強することが増えてきましたね。

配列は、記号 @ を使います。@名前 ってあったら、それは配列です。 配列は、連想配列と同じような、変数の集まりです。 でも、連想配列のように名前でアクセスするんじゃなくて、 番号でアクセスします。番号は 0 から 1、2…というような整数です。 配列に値を格納するには、

$配列名[番号] = 値;

とします。連想配列のときと同じように、値を入れるときは頭の記号が $ になります。 値を利用するときは、、

$配列名[番号]

とするだけでいいです。

split 関数は、処理の結果として、複数の文字列を返すんですが、 それぞれには連想配列のような名前とかありません。 それぞれ、対等のものっていうか、単なる、複数の文字列です。 こういう時、便利なのが配列。配列を使えば、文字列の数が1個でも何個でも、 @名前 ってするだけでこれを表すことができます。

さて、split

たとえば、区切り文字が = で文字列が「input1=text1」なら、split すると 「input1」「text1」という2つの文字列になります。 この場合、配列の番号 0 には「input1」、番号 1 には「text1」が入ります。 使い方は、

@data = split('=', "input1=text1");

とすると、

@data[0] (input1 が入っています)
@data[1] (text1 が入っています)

というようになります。…これって、今やりたいことそのまんまですよね! 文字列を特定の文字で区切って複数の文字列に分けたい、そんなときは split で決まりですね。

作ってみましょう

連想配列、split。この2つと、オマケに配列というものが出てきました。 ひとまず以上の知識で、プログラム開始といきましょう。

結果

作成過程

まず、ブラウザから送られてきたデータを受け取ります。 これには、今まで使ってきた getformdata関数でOKです。 GETメソッド、POSTメソッドの区別なく、送られてきた文字列を 受け取ることができるんでした。

$rawdata = plab::getformdata();

これで $rawdata にデータが入りましたね。 この時点では、= やら & やらが含まれた1つの文字列です。 たとえば次のような。

input1=text1&input2=text2&input3=text3

次にすることは、これを & で区切って複数の文字列に分けることです。これには split を 使うんでしたね。

@inputs = split('&', $rawdata);

これで、配列 inputs に何個か分かりませんが文字列が入りました。(上の例だと3つですね。) 今、& で区切って複数の文字列に分けましたから、それぞれは「名前=値」という形の複数の文字列に 分けられているわけですね。

そしたら次にすることは、それぞれの「名前=値」を = で区切って $name と $val に入れる処理です。 ここでも split を使いますが、今度はちょっと違う形式を使いましょう。

($name, $val) = split('=', $inputs[0]);

split は配列を返すって前に書きましたが、返ってくる文字列の数が分かっている場合は こういうふうに (変数名, 変数名, ...) という形でデータを受けることができるんです。 こうすると、例えば $data[0] に名前が入っていて $data[1] に値が入っていて… みたいに考えるかわりに、$name に名前が入っていて $val に値が入っていて… というように考えることができて、適切な名前の変数にしておけば、凄くわかりやすいですよね! なので、split するときで、数が分かっていて、それぞれになにか意味がある場合は、 こういう風な形式で適切な変数名にしておいた方がいいですね。

さて、続きです。$inputs に、何個か分かりませんが「名前=値」というのが入ってるんでした。 やりたいことは、$inputs に入っている全ての「名前=値」に対してもう一度 = で split、です。 これ、「配列の全ての要素に対してなにか処理をしたい」っていう状況ですが、 どうしたらいいんでしょう? この状況ってすごくよくでてきます。だから、そのやり方をしっかり研究しておきましょう。

配列の全ての要素に対してなにか処理を行いたいときは、foreach というものを使います。

foreach $変数 (@配列) {
	なんか処理
}

こうすると、配列の要素すべてに対して処理が行われます。 処理対象となっている配列の要素の内容は、foreachの後ろの $変数 に入っています。

では、foreachを使って、配列@inputsの全ての要素に対して、= で split しましょう!

foreach $input (@inputs) {
	($name, $val) = split('=', $input);
}

なんかできてきましたねー。 あとは、連想配列に入れる処理です。 $name と $val に名前と値が入っていますから、 連想配列に入れる処理を足すと…

foreach $input (@inputs) {
	($name, $val) = split('=', $input);
	$formdata{$name} = $val;
}

おっと完成?まとめると、

$rawdata = plab::getformdata();

@inputs = split('&', $rawdata);

foreach $input (@inputs) {
	($name, $val) = split('=', $input);
	$formdata{$name} = $val;
}

変数 $rawdata に1行になってるやつを入れるでしょ、 これを & で区切って配列 @inputs に入れるでしょ、 そのそれぞれに対して、= で区切って $name と $val を取り出して、 連想配列 %formdata に格納…、うん、できてますね!

なんか、思ったよりずっとシンプルでした。 処理そのものは、簡単なようで結構複雑ですよ。 でも、なんか、やりたいことをやってくれる機能がもともとあったというか、 ここで使ったPerlの機能、split、配列、foreach、連想配列、といった機能が とてもシンプルで便利に使えたから、こんなにシンプルなプログラムになりました。 Perlという言語が CGIプログラムによく使われるっていうのも、納得の瞬間です。

関数にしましょう

ここで作ったものは、「フォームデータを連想配列に入れて名前で 値が取り出せるようにする」っていうものでした。これ、凄くいいものです。 後から何度でも使えるものです。getformdata関数を作ったときと同じように、 関数にしておきましょう。というよりも、これって getformdata関数の機能に しちゃっていいかも知れませんね。getformdata関数の中に入れちゃいましょう。 今までgetformdata関数は、1行につながったやつを返していましたが、 連想配列を返すように仕様変更です。まだ getformdata関数を 使っているCGIプログラムは少ないし、仕様を変えてもいいっしょ (^^;

ということで、作成したプログラムは次のようになりました。 賢くなったgetformdata関数を使って連想配列を取得して、 その中に入っている input1 の値と input2 の値を画面に表示します。

作成したCGIプログラム

getformdatatest.cgi
#!/usr/bin/perl

require 'getformdata.pl';

print "Content-type: text/html\n";
print "\n";

%formdata = plab::getformdata();

print "input1 : $formdata{'input1'}<br>";
print "input2 : $formdata{'input2'}<br>";
getformdata.pl
package plab;

sub getformdata
{
	local $rawdata;
	local %formdata;
	local @inputs;
	local($input, $name, $val);

	if ($ENV{'REQUEST_METHOD'} eq "POST") {
		read(STDIN, $rawdata, $ENV{'CONTENT_LENGTH'});
	}
	elsif ($ENV{'REQUEST_METHOD'} eq "GET") {
		$rawdata = $ENV{'QUERY_STRING'};
	}

	@inputs = split('&', $rawdata);

	foreach $input (@inputs) {
		($name, $val) = split('=', $input);
		$formdata{$name} = $val;
	}

	return %formdata;
}

1;

実行結果

下の入力ボックスに文字列を入力して、送信ボタンを押してください。 別窓が開きます。

input1
input2

考察

動きました

ちゃんと動きました!input1、input2それぞれに入力した文字列がちゃんと その名前で連想配列に入ってました。getformdata関数はちょっと長くなりましたが、 cgiファイルの方では %formdata = plab::getformdata(); ってするだけ。 便利です。嬉しくなっちゃいます。

localにするのを忘れずに

関数の中で使う変数は local で「この関数の中だけで使いますよ!」ということを 宣言しておくんでしたね。配列や連想配列も同じように local しておくことを 忘れないようにしましょう。plabパッケージの中の関数なので、local しなくても たぶんトラブルが起こることは無いと思いますが、でもこれは忘れずにやっておかなくちゃ いけません。ていうか、なにもしなくても自動的に local になればいいのに…ね。

分かったこと

  1. HTMLのフォームの定義で各入力ボックスに名前を付けることが出来るんでした。
    CGI側プログラムでは、この名前で入力されたデータを区別できます。(ここではinput1とinput2。)
  2. 「名前=値」のペアを格納するときは連想配列、これで決まりです!
  3. split関数は、文字列をある文字で区切って複数の文字列(文字列の配列)にしてくれます。
  4. split関数の結果は、($a, $b, ...)という形式で受け取ることもできます。
  5. 複数フォームの場合、入力フォーム毎のデータが & で区切られ、各入力フォームにつき、
    名前と値が = で区切られているので、&、= の順で文字列を分割していくとうまくいきます。
  6. 配列の全ての要素になにか処理したいときは、foreachというものを使います。
  7. 関数は連想配列を返すこともできます。
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。