ColdFusion-開発効率を求められる今だから知りたい高性能Webアプリケーションサーバー

第5回ColdFusionでHTMLフォームのデータを扱う

第4回目の記事ではColdFusionでグラフを描画する例をご紹介しました。他の言語でグラフ描画をしようと思った場合、GDなどをインストールした上でラッパーライブラリーを介して操作するなど、グラフを描画するにも非常に煩雑な操作が伴います。対してColdFusionではcfchartタグcfchartseriesタグcfchartdataタグを用いて、HTMLのテーブルを構築するのと同じような感覚で、タグを記述するのみでグラフ描画を行うことができると分かりました。

今回はHTMLのフォームから送られてくるデータの処理の仕方を学びます。

"スコープ"という概念

今まで紹介していなかったColdFusion独自の概念として「スコープ」と言うものがあります。スコープとは、他の言語で言うところのローカルスコープ、グローバルスコープとは若干違った意味合いになります。

今までの例で何気なく使っていた変数は、全て『Variablesスコープ』というスコープの中に入っていました。例えば以下の例を実行してみましょう。以下の例ではクエリーを発行し、結果をqryという名前の変数に代入しています。また、strという名前の変数に「ColdFusion 8」という文字列を代入しています。その後、Variablesスコープをcfdumpしています。{cf_root}フォルダ({cf_root}はColdFusionをインストールしたディレクトリです。Windowsでは『C:\ColdFusion8』になります)内にある『wwwroot』の中に『gihyo』という名前のフォルダを作り、その中に『5』というフォルダを作ってください。⁠5』のフォルダの中に以下のような中身のファイルをvariables.cfmとして保存してください(ファイルのパスは{cf_root}/wwwroot/gihyo/5/variables.cfmになります⁠⁠。なお保存時の文字コードはUTF-8としてください。その後、http://127.0.0.1:8500/gihyo/5/variables.cfmでアクセスしてみましょう。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfcontent type="text/html; charset=UTF-8">
<cfquery datasource="cfartgallery" name="qry">
  SELECT
    COUNT(art.artID) AS num, art.mediaID
  FROM
    art
  GROUP BY
    mediaID
</cfquery>
<cfset str = "ColdFusion 8">
<cfdump var="#Variables#">
図1 variables.cfmの実行結果
図1 variables.cfmの実行結果

ご覧頂いて分かるとおり、Variablesスコープは構造体です(cfdumpしたとき青色になるものは構造体です。また「struct」というラベルが付いています⁠⁠。そして構造体のキーとして「QRY」「STR」があり、その中にクエリーの結果や代入した文字列が保持されていることが分かります。つまり、今まで何気なく使用していたページ内で定義した変数は全てVariablesスコープという構造体の中に保存されていることがわかります。

また、cfdumpの結果を見て分かるとおり、ColdFusionは変数の大文字小文字を区別しません。従って、⁠Variables.qry」と書いても「Variables.QRY」と書いても同じ変数を参照します。しかしながら、変数の宣言時と参照時で違う綴りをしているとプログラミングミスに繋がる可能性があるため、CFMLテンプレート中では綴りは統一しておくことが望ましいでしょう。

HTMLフォームのデータを保持するFormスコープ

HTMLフォームからPOSTで送ったデータはFormスコープというVariablesスコープとは別のスコープに保存されます。以下の例を{cf_root}/wwwroot/gihyo/5/form.cfmとして保存してください。form.cfmにアクセス後、テキストフィールドに適当な文字を入力し「送信」というボタンをクリックしてみましょう。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfcontent type="text/html; charset=UTF-8">
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<title>Formスコープ</title>
<cfif StructKeyExists(Form, 'submit')>
  <cfset str = "ColdFusion 8">
  <cfdump var="#Variables#" label="Variablesスコープの中身">
  <cfdump var="#Form#" label="Fromスコープの中身">
</cfif>
<form action="form.cfm" method="post">
  <p>何かテキストを入力してください:<input type="text" name="str" value=""><br>
  <input type="submit" name="submit" value="送信" ></p>
</form>
図2 form.cfm実行結果。⁠あいうえお」と入力して送信した。
図2 form.cfm実行結果。「あいうえお」と入力して送信した。

ご覧頂いて分かるとおり、POSTで送信されたデータがFormスコープに保持されていることが分かります。また、Variablesスコープとは別扱いになっていることも分かると思います(フォームと変数で同じ「str」という変数名を使っていますが、スコープが別になっているため上書きされていません⁠⁠。ちょうどPHPでPOSTで送信された値が$POSTという連想配列に代入されるのと同じような感覚です。

なお、FormスコープもVariablesスコープと同様に構造体のため、変数の大文字小文字を区別しません。

さて、ここでいくつか見慣れないコードが出てきます。

StructKeyExists()関数は、第一引数に渡された構造体に、第二引数で渡されたキーが存在するかどうかを返す関数です。従って、StructKeyExists(Form, 'submit')とは、Formスコープに「submit」というキーが存在するかどうかを判定しています。つまり、cfifの部分で『HTMLフォームからデータが送信されたかどうか』を判定しています。

同じように変数が定義されているかどうかを調べる関数にはIsDefined()関数がありますが、こちらは少々厄介です。例えばIsDefined('Form.submit')とすると、Formスコープの中に「submit」という変数が定義されているかどうかを調べることもできますが、仮にそれが存在しなかった場合、Variablesスコープの「Form」という名前の構造体に「submit」と言うキーが存在するかどうかも調べてしまいます(つまりIsDefined('Variables.Form.submit')を実行しているのと等価です⁠⁠。このようにIsDefined()関数は、参照可能な値をすべて辿っていって、定義されているかどうかを調べてしまうので、意図しない値を返すことがあります。また、参照可能な値をすべて辿るため、パフォーマンスの観点からもあまりお奨めできません。したがって、あるスコープに変数が定義されているかどうかを調べるのにはStructKeyExists()関数を使いましょう。

次に見慣れない関数としてSetEncoding()関数があります。これは、POSTで渡されてくるデータの文字コードを指定するものです。今回はUTF-8のページから自分自身にPOSTしているため、POSTの文字コードはUTF-8であると明示しています。例えば、文字コードがShift_JISのページからこのファイルにPOSTする場合は、<cfset SetEncoding("Form", "Shift_JIS")>と、Shift_JISのデータが送られてくると明示する必要があります。

フォームの値にアクセスする

Formスコープの値にアクセスするには「Form.変数名」とします。⁠変数名」だけでもアクセスすることは可能ですが、Variablesスコープに同名の変数があった場合、そちらが優先して参照されてしまうので、フォームの値にアクセスすることを明示するため、⁠Form.」を変数名の先頭に付ける癖をつけましょう。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfcontent type="text/html; charset=UTF-8">
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<title>Formスコープ</title>
<cfif StructKeyExists(Form, 'submit')>
  <cfset SetEncoding("Form", "UTF-8")>
  <cfoutput><p>フォームから送信された値:#Form.str#<p></cfoutput>
</cfif>
<form action="xssForm.cfm" method="post">
  <p>何かテキストを入力してください:<input type="text" name="str" value=""><br>
  <input type="submit" name="submit" value="送信" ></p>
</form>

フォームから送信された値を出力するにはこのようなコードを書いてしまいがちですが、このフコードはXSS(Cross-site Scripting)脆弱性を含んでおり危険です。例えば上の例をxssForm.cfmとして保存しアクセスした後、テキストフィールドに「<script type="text/javascript">alert("hello")</script>」と入力し、送信してみましょう。アラートボックスが表示されたかと思います。

図3 ⁠<script type="text/javascript">alert("hello")</script>」と入力し送信してみた例
図3 

このようにページ制作者が意図しないHTMLコードを紛れ込ませることができるような状況は危険です。何らかの値をページ中に表示させるときは、以下のように必ずHtmlEditFormat()関数を通すようにしましょう。先回解説したとおり、HtmlEditFormat()関数はHTMLで用いられる特別な文字(⁠⁠<」「&」など)をエスケープしてくれる関数です。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfcontent type="text/html; charset=UTF-8">
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<title>Formスコープ</title>
<cfif StructKeyExists(Form, 'submit')>
  <cfset SetEncoding("Form", "UTF-8")>
  <cfoutput><p>フォームから送信された値:#HtmlEditFormat(Form.str)#<p></cfoutput>
</cfif>
<form action="non-xssForm.cfm" method="post">
  <p>何かテキストを入力してください:<input type="text" name="str" value=""><br>
  <input type="submit" name="submit" value="送信" ></p>
</form>
図4 non-xssForm.cfmで「<script type="text/javascript">alert("hello")</script>」と入力し送信してみた例
図4 non-xssForm.cfmで

URLスコープ

Formスコープ同様、クエリーストリング(例えば「http://example.jp/test.cfm?id=1234」「id=1234」の部分)にもアクセスすることができます。クエリーストリングはUrlスコープというスコープに保存されます。以下のファイルを{cf_root}/wwwroot/gihyo/5/url.cfmとして保存し、http://127.0.0.1:8500/gihyo/5/url.cfm?q=%E3%81%82でアクセスしてみましょう。

<cfprocessingdirective pageEncoding="UTF-8" />
<cfcontent type="text/html; charset=UTF-8">
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<title>Urlスコープ</title>
<cfset SetEncoding("Url", "UTF-8")>
<cfdump var="#Url#" label="Urlスコープの中身">
図5 url.cfm?q=%E3%81%82の実行結果
図5 url.cfm

ご覧頂いて分かるとおり、UrlスコープもFormスコープ同様に構造体で、⁠Q」というキーに「あ」という値が入っているのが分かると思います。Urlスコープも変数の大文字小文字を区別しません。従って「url.cfm?q=%E3%81%82」でアクセスしても「url.cfm?Q=%E3%81%82」でアクセスしても同様の結果が得られます。

また、クエリーストリングにある値が渡されているかどうか調べるには、Formスコープの時と同様にStructKeyExists()関数を使います。例えばクエリーストリングに「q」というキーが渡されているかどうかを調べるにはStructKeyExists(Url, 'q')とします。

まとめと次回予告

今回はまずスコープという概念を学びました。CFMLテンプレート中で宣言した変数は、すべてVariablesスコープ(実態は構造体)に格納されることが分かりました。

また、フォームでPOSTされた値はFormスコープへ、クエリーストリングで渡された値はUrlスコープに格納されることが分かりました。

そして、ある名前の変数が特定のスコープに存在するかどうかを調べる方法を学びました。StructKeyExists()関数の第一引数にスコープを、第二引数に変数名を渡せば、定義されているかどうかを調べることができます。IsDefined()関数でも同様のことができますが、IsDefined()関数は参照可能な値を全て辿っていってしまうため、目的のスコープ以外で同名の変数が定義されていた場合も定義されていると返してしまうため、プログラムが意図しない動作をしてしまう可能性があることが分かりました。

次回は、ColdFusionがAdobe製品群となったことで強化されたPDF周りの機能をご紹介します。

おすすめ記事

記事・ニュース一覧