学生情報管理 アプリケーション

学生情報管理
アプリケーション
国際文化学部
国際文化学科
05g0103
大澤
1
裕
目次
0.はじめに
1.アプリケーション概要
1-1.動作環境
1-1-1.PHPとは
1-1-2.環境の構築
1-2.全体のフロー
1-3.学生新規作成
1-4.学生検索
1-5.学生データ編集
1-6.学生データ削除
1-7.メール入力
1-8.メール出力
1-9.CSV データエクスポート
1-10.CSV データインポート
2
2.データベースの構造
2-1.正規化について
2-2.genders テーブル
2-3.prefs テーブル
2-4.kas テーブル
2-5.nens テーブル
2-6.customers テーブル
2-7.データベース作成用DDL
3.アプリケーション詳細
3-1.各画面とスクリプトの相関図
3-1-1.スクリプト一覧
3-2.共有ファイル(db_init.php)
3-3.学生新規作成(editStudent.php)
3-4.学生検索(searchStudent.php)
3-5.学生削除(delete.php)
3-6.メール入力(mailInput.php)
3-7.メール送信(sendJob.php)
3-8.CSV エクスポート(exportCsv.php)
3
3-9.CSV インポート(importCsv.php)
4.改善点、反省
5.謝辞
6.参考文献
4
0.はじめに
このアプリケーションは、学生の個人データを管理することを目的とした Web アプリケー
ションである。
また、このアプリケーションを応用すれば、学生という範囲に留まらず、様々な団体や個
人のデータ管理をすることが可能である。
作成するスクリプトは、以下のディレクトリに保存する。
このディレクトリ下にスクリプト作成
保存したスクリプトを実行するには、ブラウザで以下のような URL にアクセスする。
http://localhost/part5/kanri/<ファイル名>
5
1.アプリケーション概要
1.動作環境
①PHP(5.2.3)
②Apache(2.2.4)(Webサーバ)
③MySQL(5.0.45)
(データベース)
1-1.PHPとは
◆PHPの歴史
誕生は1995年頃で、個人的な利用が前提の「Personal Home Page Tools」として開
発されたスクリプト処理系が始まりである。1995年当時は限られた Web 用のツールし
かなかったため、そこから急速にオープンソースとして開発が進められていく。
幾度かの機能拡張を経て、1998年頃にPHP3として公表された。このバージョン
から一気に普及していったといえるだろう。1998年頃は Java や ASP もサーバサイドで
の利用が本格化した、サーバサイド Web 開発のターニングポイントであった。
その後も更に拡張は続けられ、2000年にPHP4がリリースされた。PHP処理エ
ンジンの大幅な刷新により、速度の向上やクラスなどの新機能も取り入れた。
そして2004年にPHP5が公表された。このバージョンではPHP処理エンジンも
バージョンアップし、PHPの構文も従来との互換性に留意しつつオブジェクト指向の機
能も多く取り入れられている。
このように急速に開発が進められた結果、様々な利用者の声が取り入れられた極めて現
実主義的な実行形式に変換するスクリプトエンジンは、高く評価されている。
6
◆サーバサイドのスクリプト言語
PHPは Web アプリケーション作成に特化したサーバサイドスクリプト言語である。
PHPの特徴を表現するキーワードとして、「サーバサイド」、「スクリプト」というポイン
トがある。この観点からPHPの特徴を見ていく。
◇サーバサイド/クライアントサイド
Web アプリケーションを作るうえで、そのプログラムがどこで実行されるかに視点を置い
た分類である。サーバ側で実行されるものが「サーバサイド」であり、クライアント側(ブ
ラウザ上)で実行されるものが「クライアントサイド」である。
サーバサイドで実行されるものには Perl や C 言語を用いた CGI、Java によるサーブで実
行される。
・サーバサイドのメリット、デメリット
・メリット
基本的にインストールがサーバ側だけで済み、クライアント側にはブラウザさえあればい
いという、導入の容易さがある。インターネットではもちろん、イントラネットで使う場
合でも、このメリットは大きいといえる。
・デメリット
負荷がサーバマシンに集中するため、プログラムの処理が複雑な場合にアクセス数の増加
に耐えにくい傾向がある。
7
一方、クライアントサイドで実行されるプログラムには、JavaScript や Flash アプリケ
ーション、少数だが Java アプレットなどもこの一種である。
・クライアントサイドのメリット、デメリット
・メリット
クライアント側で動作するため、実行に際してサーバのマシンパワーを必要としない。多
くのクライアントがアクセスしても、最新のダウンロードが済んでしまえばサーバは処理
から開放されるため。
・デメリット
アクセスに利用しているブラウザが果たしてそのアプリケーションの実行に適した環境な
のかわからない。JavaScript の場合は実装依存性がある。IE(Internet Explorer)と Firefox
で動作が異なるという現象は珍しくない。
◇コンパイル言語/スクリプト言語
プログラミング言語を大きく二分すると、コンパイル言語とスクリプト言語に分けるこ
とができる。コンパイル言語はいったんソースファイルをコンパイルして、実行可能形式
のファイルとして作り直す必要のあるもの、スクリプト言語はソースファイルを書きさえ
すればすぐに実行できる。
・コンパイル言語
コンパイルという作業上のステップを取り入れることにより、その段階で型チェックや構
文チェックなどを厳密に行うものが多く、作成上のケアレスミスなどを実行前にチェック
できるメリットがある。プログラムの規模が大きくなって複雑さがある一定ラインを超え
たら、この事前チェック機能は必須だともいわれている。コンパイルによって実行可能(ま
たは実行しやすい)ファイル形式に作り直されるため、一般論として実行速度が速いとも
いわれている。
8
・スクリプト言語
すぐに実行できるため非常に手軽。開発作業が軽快に進められる。これは大きなメリッ
トである。また、スクリプト言語に多い傾向として、型や構文に大幅な柔軟性を取り入れ
ている言語が多く、コンパイル言語とは正反対の特徴、つまり型に対する配慮がほとんど
必要ないものが大半である。PHPはスクリプト言語である。
図:コンパイル言語とスクリプト言語
先に、PHPは Web アプリケーション作成に特化した言語と記したが、これは昨今様子
が変わってきている。昔は Web 専用言語として誕生して休息に発展してきたのだが、現在
は単独で実行可能な処理系も登場し、その利用局面も大幅に広がっているということがで
きるだろう。
9
◇PHPの特徴
上記以外のPHPの特徴としては、たくさん提供されている関数の量があげられる。P
HPでは、ある程度処理量の機能を切り出したものを関数としてまとめることができる。
しかし、それを独自に開発する必要がないほどに多種多様な事前定義関数が提供されてい
る。しかも、それはかなり高速に動作する。そのため、下手に自前で関数を作るよりは既
存の関数を探すことに時間を割いた方がいい場合も少なくないだろう。
また、時代の流れとしてソフトウェア開発で主流となりつつあるオブジェクト指向も取
り入れるようになってきている。まだ十分とはいえないが、大規模アプリケーションでは
避けて通れないオブジェクト指向的な手法を取り入れることも可能である。
実運用で一般的に使われる UNIX(Linux)環境と Windows 環境をサポートしていることも重
要なポイントである。これにより、開発段階ではプラットフォームとして Windows を使い、
実際にテスト・運用する段階以降は UNIX に移行するということが比較的容易だ。ただし、
スクリプト内で環境に依存する命令や関数を使っている場合は、若干の修正が必要になる
こともある。
◇まとめ
PHPはサーバサイドスクリプトなので、Web サーバ上に環境を用意する。PHPプログ
ラムは、その Web サーバにアクセス可能な Web クライアントさえあれば実行可能である。
PHPはまた、スクリプト言語である。そのため、コンパイルをすることなくプログラ
ムを実行させることができる。
10
1-2.環境の構築
①PHP5のインストール
展開先ディレクトリは C:\Program Files\PHP5
②php.ini のコピーと編集
PHPエンジンは php.ini という設定ファイルを使用する。
展開したファイルの中を探すと、php.ini-dist というファイルがある。このファイルが
設定のテンプレートファイルで、この設定ファイルを修正する形で設定を行う。
まず、php.ini-dist を「php.ini」というファイル名で同ディレクトリ内にコピーする。
そのファイルの中で以下の修正を行う。
修正1.include_path 修正
ファイル内の「include_path」という記述を探す(462 行目付近)。ここには、スクリプ
トのカレントディレクトリと、それ以外に指定したいサーチデバイスを指定する。
; Windows: “path1;\path2”
;include_path = “.;c:\php\includes”
include_path = “.;C:\Program Files\PHP5\includes”
←----------追加する
修正2.extension_dir 修正
拡張モジュールを格納したディレクトリのパスを指定する(475 行目付近)。拡張モジュ
ールはPHPインストールディレクトリ中の「ext」サブディレクトリにある(今回は
「C:\Program Files\PHP5\ext」となる)。
; directory in which the loadable extensions (modules) reside.
; extension_dir = “./”
←-------------コメントアウトする
extension_dir = “C:\Program Files\PHP5\ext”
11
←-------------追加する
修正3.extension 有効化
コメントを解除して拡張モジュールを追加する(590 行目付近)。ここでは日本語処理の
ためのモジュールと画像処理のためのモジュール、MySQLを利用するためのモジュー
ルを読み込むようにコメントを解除する。
; Windows Extensions
; Note that ODBC support is built in, so no dll is needed for it.
.
.
.
extension=php_gd2.dll
extension=php_mbstring.dll
extension=php_mysql.dll
extension=php_pdo.dll
extension=php_pdo_mysql.dll
←--------以上5行をコメント解除
修正4.短縮タグの使用
コ メ ン ト で 「 Language Options 」 と し て 示 さ れ た グ ル ー プ が あ る が 、 こ こ に
「short_open_tag」の設定が 85 行目付近にある。これは簡略化したタグの使用を設定する
もの。PHP5.2.3 の場合は初期状態で On になっているので確認すること。
short_open_tag = On
12
修正5.mbstring の動作設定
[mbstring] と い う セ ク シ ョ ン が 1180 行 目 付 近 に あ る の で 、 そ の 中 か ら
「mbstring.internal_encoding」を探し、コメントをはずした後、下記のように修正する。
[mbstring]
; Language for internal character representation.
;mbstring.Language = Japanese
; internal/script encoding.
; Some encoding cannot work as internal encoding.
; (e.g. SJIS, BIG5, ISO-2022-*)
mbstring.internal_encoding = SJIS ←------------------------------このように修正
ここでは漢字やひらがななどの2バイト文字を扱うための mbstring モジュールが内部で
使用する文字エンコーディングを指定している。Windows の場合はシフト JIS が標準なので
「SJIS」にしておくと便利。
③.MySQL利用の準備
本アプリケーションではデータベースとしてMySQLを利用するとしたが、そのため
の準備をここで行う。C:\Program Files\PHP5 に「libmysql.dll」というファイルがある。
これを C:\WINDOWS\system32 ディレクトリにコピーする。これにより低レベルデータベー
ス関数が利用可能になる。
以上でPHPのインストールは終了。
13
④.Apache2のインストール
Apacheは、Apache Software Foundation で開発が進められている Web サーバ(HTTP
サーバ、HTTPD)である。Windows や各種の UNIX で動作する、デファクトスタンダードとい
ってよい実績のあるソフトウェアです。業界標準への対応やセキュリティ上の問題への対
応も早く、情報も入手しやすいという特徴がある。
URL: HTTP://httpd.apache.org/
・インストール手順
1.ファイルをダブルクリックし、
「実行」
2.インストールするバージョンなどを確認して「Next」
3.ライセンスの確認が表示され、確認のうえ「I accept the terms in the license
agreement」を選択して「Next」
4.インストール前の注意事項として Readme が表示され、確認して「Next」
5.サーバを設置するドメイン、サーバの名前、管理者のメールアドレスを入力し「Next」
6.Setup Type の画面でインストールする構成を選択する。ここでは「Typical」を選択し、
「Next」
7.インストールするディレクトリを選択する。
8.「install」により、ファイルのコピーと設定の反映が行われる。
9.「Finish」で完了
10.ブラウザを起動して http://localhost/ にアクセスし、「It works」という画面が表
示されたら成功。
⑤.設定ファイルの編集
Apache2にPHPを認識させるため、Apacheの設定ファイルを修正する。
修正1.httpd.conf の修正
Apacheのインストールディレクトリに conf サブディレクトリがある。このファイ
ルに、LoadModule というディレクティブ(命令)が並んでいる部分(67 行目付近)がある
ので、末尾に以下の2行を追加する。
LoadModule php5_module “C:\Program Files\PHP5\php5apache2_2.dll”
PHPiniDir “C:\Program Files\PHP5”
この修正でApacheはPHPエンジンと php.ini の場所を理解する。
14
次に 210 行目付近の DirectoryIndex ディレクティブに「index.php」を追加する。これ
により、URL にディレクトリまでしか指定しなかった場合でも、「index.html」、もしくは
「index.php」が自動的に開かれるようになる。
<IfModule dir_module>
DirectoryIndex index.html index.php
</IfModule>
最後に 450 行目付近の以下の部分のコメントを外す。これにより extra サブディレクト
リ下にある http-languages.conf が有効になる。このファイルはApacheで多言語処
理を行う場合の設定を集めたファイルである。
# Language settings
Include conf/extra/httpd-languages.conf
←----------------------コメントを外す
修正2.mine.types の修正
次に、どのファイルに対するリクエストがPHPエンジンの処理を必要としているかも
Apacheに教えなければならない。
Apacheではメディアタイプ(メッセージボディに含まれているデータの種類)と
ファイルの関連付けを conf ディレクトリ内の mine.types というファイルで設定する。次
の内容を追加する。
application/x-httpd-php
15
修正3.httpd-languages.conf の修正
最後に conf ディレクトリ内の extra ディレクトリ内にある http-languages.conf を修正
する。まず 19 行目付近の「DefaultLanguage」を以下のようにして、標準の言語を日本語
にする。
# * It is generally better to not mark a page as
# * being a certain Language than marking it with the wrong
# * Language!
#
DefaultLanguage ja
←---------------------------このように修正
次にファイルの末尾に以下の設定を追加する。
addDefaultCharset ディレクティブは HTTP
レスポンスのコンテントタイプが text/plain、あるいは text/html の場合に追加するデフ
ォルトの charset パラメータを設定するものである。
AddCharset iso-10646-ucs-4 .ucs-4 .iso-10646-ucs-4
AddCharset shift_jis
.shift_jis .sjis
AddDefaultCharset shift_jis
←---------------------------この行を追加
これで、ApacheとPHPの連携ができるようになる。
Apacheの設定ファイルを修正し終わったら、サービスを再起動(Restart)して動
作確認をする。これは、タスクトレイのApacheアイコンを右クリックして「Open
Apache Monitor」を選択して行う。
これで、変更した設定ファイルが反映される。
またApacheのドキュメントルートは、デフォルトでは「C:\Program Files\Apache
Software Foundation\Apache2.2\htdocs\」となっている。
16
⑥.MySQL5のインストール
MySQLは MySQL AB によって開発が進められているオープンソースの RDBMS で、高速
なクエリが特徴である。
Linux 版や Windows 版があり、テストや開発環境はもちろんのこと、
実運用でも広く使われている。RDBMS とは、リレーショナル・データベースを管理するため
のソフトウェアの総称である。
URL: http://www.mysql.org/
・インストール手順
1.setup.exe を得て、ウィザード開始画面で「Next」
2.セットアップのタイプ選択画面で Typical(標準)を選択し、「Next」
3.インストールするファイルとインストール先ディレクトリ確認画面で「Install」
4.ファイルのインストール中には、「MySQL Enterprise」の宣伝画面が表示されるが、2
回「Next」で閉じる。
5.「Configure the MySQL Server now」にチェックが入っていることを確認し、「Finish」
6.構成ウィザードに進むので、「Next」
7.MySQL サーバの設定方法選択画面で「Detailed Configuration」にチェックを入れて
「Next」
8.「Developer Machine」にチェックを入れて「Next」
9.「Multifunctional Database」をチェックし、
「Next」
10.データ格納ドライブ選択画面でデフォルトのまま「Next」
11.同時接続数はデフォルトの「Decision Support(Dss)/OLAP」を選択して「Next」
12.データベースサーバへの接続方法設定画面で、デフォルトのまま「Next」
13.デフォルトの文字設定画面で、「Manual Selected Character Set/Collation」を選択
し、右側のドロップダウンから「sjis」を選択し、「Next」
14.データベースサーバを Windows サービスとしてインストールするか選択する画面で、
「Install As Windows Service」にチェックし「Next」
15.データベースサーバの管理者アカウント設定で、パスワードを設定し「Next」
16.内容を確認し、「Execute」し、完了
17
⑦.PEARライブラリ
PEARは「PHP Extension and Application Repository」の略で、PHPの機能を拡
張するためのライブラリ群である。
URL: http://pear.php.net/
◆PEAR::DB モジュールについて
PEARライブラリの DB クラスでは、RDBMS に接続する際に DSN(Data Source Name)と
呼ばれる設定を使うが、ここで RDBMS を指定する。実際のアクセスはクラスの内部で行わ
れているため、作成するアプリケーションはそれを意識する必要が無い。そのため、接続
する RDBMS を変更するだけで済む。
また、DB クラスではプレースホルダと呼ばれる機能をサポートしている。この機能は、
SQL を動的に構成するための機能で、SQL インジェクションと呼ばれる攻撃に根本から対策
することができる。
◆PEAR::DB モジュールの準備
PEAR::DB モジュールは、DB アクセスのためのクラスライブラリである。PHPのデ
フォルトではインストールされていない。PEAR::DB のような標準添付されないモジュ
ールをインストールする場合は、PEARのパッケージマネージャを使用できる状態にし
なければならない。コマンドプロンプトを起動し、PHPをインストールしたディレクト
リに移動し、
「go-pear.bat」を実行する。
・準備手順
1.(system|local) [system] :
←---------------------[Enter]を押す
2.1-10, ‘all’ or Enter to continue:
←---------------------[Enter]を押す
3.Would you like…………php.ini>? [Y/n]
←--------------------------Yを押す
これでパッケージマネージャが使用可能になる。php.ini を変更したので、Apache
を再起動しておく。インストールしたパッケージマネージャを使用して、PEAR::DB モ
ジュールをインストールする。
4.C:\Program Files\PHP5>pear install DB=1.7.10
←-------------このように入力
5.C:\Program Files\PHP5>pear list と入力し、「DB」の行があれば完了。
18
⑧.QuickForm
PEAR::QuickFormは、フォームを自動生成するためのライブラリである。
提供する主要な機能要素には、フォームの要素が多数と、ルール、フィルタがある。フォ
ームの要素はテキストフィールドやボタンなどのコンポーネントで、これらをPHPのソ
ースコードで宣言的に記述する。
ルールとは、バリデーション(フォームに入力された値が有効かどうかチェックするこ
と)のための規則である。「必須項目」、「数字を入力してはならない」といった、細かいル
ールを設定できる。
フィルタとは、値を最終的に確定する前に処理する変換のことである。例えば入力値の
前後についている不要な空白を除去するといったフィルタは日常的に使う。
・導入方法
PEAR::QuickFormを使用するには、モジュールをインストールする必要が
ある。PEARのインストールモジュールは先ほどインストール済みなので、PHP5を
インストールしたディレクトリに移動して、次のように実行する。
C:\Program Files\PHP5>pear install HTML_Common-1.2.4
C:\Program Files\PHP5>pear install HTML_QuickForm-3.2.7
※必ず「HTML_Common」を先にインストールすること。そうでないとエラーになる。
19
2.全体のフロー
このアプリケーションは、学生データベースを管理するためのものである。現段階では
個人的な使用のみを目的としているので、管理者用/利用者用の区別はつけていない。つま
り、ログイン機能も存在しない。
以下に主要な画面をあげる。
・主要8画面
1.学生新規作成
(editStudent.php)
2.学生検索
(searchStudent.php)
3.学生データ編集
(editStudent 流用)
4.学生データ削除
(delete.php)
5.メール入力
(mailInput.php)
6.メール送信
(sendJob.php)
7.CSV出力
(exportCSV.php)
8.CSV取り込み
(importCSV.php)
各学生は、以下のデータを持つ。
・学生の持つデータ
1.学籍番号
2.学科
3.学年
4.名前
5.性別
6.都道府県
7.メールアドレス
8.電話番号
この学生 DB を追加、削除、編集でき、検索したうえで学生にメールの個別/複数送信が
できる。また、学生リストを CSV として出力したり、同様の CSV から入力することもでき
る。
画面上部には「学生新規作成」、
「学生検索」、
「CSV インポート」のメニューが表示される。
その他の機能は全てこれらのメニューから順に操作してアクセスする。
20
3.学生新規作成画面
(editStudent.php)
画面の遷移は以下の通りだ。まず入力画面で必要な情報を入力し、
「送信」を押す。する
と内容確認画面に移る。確認したら再度「送信」で RDBMS に学生データとして追加される。
この画面で作成した学生データは、このアプリケーションの中心的なデータとなる。複数
人を一括で追加したい場合は、「CSV インポート」がある。
既存の学生を編集する場合は、各フィールドに DB から読み込んだデータが入力された状
態で表示される。
21
4.学生検索
(searchStudent.php)
学生検索は、DB で管理している学生から、条件に適合するデータを抽出するための機能
である。抽出した学生データに対しては、編集、追加、削除、メールを送信する、又はそ
のデータを CSV データとして出力する操作が可能である。
検索したい項目を任意で入力し、
「検索」。
22
5.学生データ編集
(editStudent 流用)
学生の検索画面から「編」のリンクをクリックすると、学生データの編集画面が表示さ
れる。この画面は学生新規作成画面を流用している。既存の学生を編集する場合は、各フ
ィールドに DB から読み込んだデータが入力された状態で表示される。
内容を変更し、「送信」で確認画面が表示され、再度「送信」で編集完了。
23
6.学生データ削除
(delete.php)
学生検索画面から「削」のリンクをクリックすると、学生データの削除画面が表示され
る。「送信」をクリックすると、表示された学生が削除される。
24
7.メール入力
(mailInput.php)
検索画面で「チェックした学生にメールを書く」をクリックすると、「メール入力画面」
に遷移する。この操作で、学生検索画面で抽出した顧客に同胞メールを送信することがで
きる。このとき、チェックボックスをオフにしておくとその学生をメール送信から除外す
ることもできる。
メール入力画面では、メールのタイトルとなる Subject とメッセージの本文を入力でき
る。
25
8.メール送信
(sendJob.php)
メール入力画面で「チェックした学生にメール送信」をクリックすると、そのときチェ
ックボックスの入っている学生に対してメールを送信する。次ページの画面は、そのメー
ル送信の結果を表示している。
このアプリケーションでは、ループで各アドレスに対して1通ずつ送信している。
上は、SMTP サーバへのアクセスがうまくいっていない状態のため、送信を失敗している。
26
9.CSV出力
(exportCSV.php)
検索画面で、リンクとして「CSV エクスポート」が表示されている。これをクリックする
と、そのときに表示されている学生一覧のデータを CSV ファイルとしてダウンロードでき
る。このデータは、Excel などの外部アプリケーションで編集することを想定している。ま
た、データのバックアップにも利用できる。
27
10.CSV取り込み
(importCSV.php)
トップメニューの「CSV インポート」をクリックすると、ファイルをアップロードして学
生を追加することができる。アップロードするファイルは、Excel やテキストエディタなど
で作成することができる、CSV(カンマ区切り)テキストファイルである。アップロード時
に、UTF-8 や EUC-JP といった、そのファイルの文字エンコーディングも指定する。指定し
なければ、スクリプトのデフォルト文字エンコーディング(Shift_JIS)が使用される。
28
2.データベースの構造
このアプリケーションで使用するデータベースは「php_customers_db」とする。こ
のデータベースに、以下の5つのテーブルを格納する。
1.genders(性別)
2.prefs(都道府県)
3.kas(学科)
4.nens(学年)
5.customers(学生情報全体)
5つのテーブルのうち、4つは固定の内容なので、アプリケーションで管理が必要なの
は1つだけである。RDBMS にテーブルを定義するときに用いる DDL はテーブル説明の後に記
載する。
1.正規化について
固定式のテーブル4つは、それぞれ性別、都道府県、学科、学年を正規化する目的で作
成した。ここで、正規化について説明する。
◆正規化とは
データベースで扱う表が、どれだけ整理されているかを測る指標に正規化がある。
無秩序にデータが詰め込まれた長い(正規度の低い)表には、様々な更新不整合が生じ、
作業に深刻な影響を与える。これを防止する根本的な対処は、正規化と呼ばれる一連の作
業を行って、表の正規度を高めることである。
・第一正規形
・特徴
表の全てのドメイン(属性値のとりえる範囲)が単純であること。単純であるとは、全
ての属性値が組み合わせや繰り返しにならないことである。
問題点1 : 行の追加ができないことがある
問題点2 : 行の削除によって情報が損失することがある
問題点3 : 行の更新によって不整合が生じることがある
29
・第二正規形
・特徴
定義として、表が第一正規形であり、非キーが主キーに完全関係従属すること。
・関係従属性
属性集合X,Y間に「Xの値が定まればYの値が一意に定まる」という関係があるとき、
YはXに関係従属する(X→Y)という。
・完全関係従属性
「X→Y」という関係従属において、XがYを定めるための最小構成であるとき、つま
りYがXの一部分に関係従属しないとき、YはXに完全関係従属するという。
・第三正規形
・特徴
定義として、表が第二正規形であり、非キーが主キーに推移的関係従属「しない」こと。
・推移的関係従属
「Xが定まればYが一意に定まり、Yが定まることによってZが一意に定まる(X→Y
→Z)」、つまりZはXに推移的関係従属するという、関係従属の三段論法のような関係。
・推移的関係従属の切断
X→Y→Zの関係を、X→YとY→Zに分割、つまりYを一方で外部キー、もう一方で
主キーにする。
◆正規化の利点と欠点
正規化によって、不整合が生じにくい整理された表を作成することができる。一方で、
正規化は表の分割を伴うため、高次に正規化された表に対して問い合わせする際には、表
を結合し直さなければならない。その分、応答性能が犠牲になるといわれている。
不整合は行の追加、削除、更新により生じ、参照では生じない。このため、更新される
可能性が低く、専ら参照に用いる表では、応答時間を優先するため正規度を低いままに保
つ場合もある。
30
2.genders テーブル
このテーブルは性別を正規化する目的がある。データの内容は次の通りである。
id
name
1
男性
2
女性
・データ型
カラム名
id
データ型・制約
INTEGER NOT NULL AUTO_INCREMENT (整数+自動連番)
name
VARCHAR(10) (可変長文字列+最大長 10)
このテーブルのデータはアプリケーションが動作する上で必須のものなので、アプリケ
ーションの初期化を行う段階で忘れずに入れるようにする。
3.prefs テーブル
このテーブルは都道府県を正規化する目的がある。このデータも動作には必須で、47
都道府県を固定的に入れておく。このテーブルも初期化のときにデータを入れること。
id
name
1
北海道
2
青森県
・データ型
カラム名
id
name
データ型・制約
INTEGER NOT NULL AUTO_INCREMENT (整数+自動連番)
VARCHAR(32) (可変長文字列+最大長 32)
31
4.kas テーブル
このテーブルは学科を正規化する目的がある。このデータも動作には必須で、34学科
を固定的に入れる。このテーブルも初期化のときにデータを入れること。
id
name
1
法律
2
政治
・データ型
カラム名
id
データ型・制約
INTEGER NOT NULL AUTO_INCREMENT (整数+自動連番)
name
VARCHAR(32) (可変長文字列+最大長 32)
5.nens テーブル
このテーブルは学年を正規化する目的がある。このデータも動作には必須で、4学年を
固定的に入れておく。このテーブルも初期化のときにデータを入れること。
id
name
1
1
2
2
・データ型
カラム名
id
name
データ型・制約
INTEGER NOT NULL AUTO_INCREMENT (整数+自動連番)
VARCHAR(10) (可変長文字列+最大長 10)
32
6.customers テーブル
このテーブルは、このアプリケーションの中心的なデータで、学生テーブルの本体であ
る。データは、RDBMS 上でもプログラム上でも重複チェックをしていないので、同じ学生が
データに含まれることもある。
id
studentnum
ka
nen
name
gender
pref
mail
phone
oo
xxxxxxxx
xxxx
x
xxxxxxxx
xx
xxxx
xxxxxxxx
xxxxxxxx
・データ型
カラム名
id
studentnum
データ型・制約
INTEGER NOT NULL AUTO_INCREMENT (整数+自動連番)
VARCHAR(32) (可変長文字列+最大長 32)
ka
INTEGER
nen
INTEGER
name
VARCHAR(32)
gender
INTEGER
pref
INTEGER
mail
VARCHAR(64)
phone
VARCHAR(64)
カラム id は学生テーブルの主キーなので、学生データを特定するために必要十分な値で
ある。このアプリケーションで頻繁に使用するデータで、以降は学生 id と呼ぶ。
このテーブルでは1行が学生1人に対応するが、あまり複雑にならないように、リレー
ションシップは学科、学年、性別、都道府県に対してのみ持っている。
33
・データ挿入の例
INSERT INTO customers (id,studentnum,ka,nen,name,gender,pref,mail,phone)
VALUES (1, 'f05g0103', 1, 4, ' 大 澤 ゆ た か ', 1, 1, '[email protected]',
'090-7777-7777');
ここで、ka に 1、nen に 4、gender に 1、pref に 1 を入れた。ka は kas テーブルを参照
するので、kas テーブルの主キーが 1 の行は法律となる。nen は nens テーブルを参照する
ので、nens テーブルの主キーが 4 の行は 4 年生である。gender は genders テーブルを参照
するので、genders テーブルの主キーが 1 の行は男性となる。pref は prefs テーブルを参
照するので、prefs テーブルの主キーが 1 の行は北海道である。つまりこの INSERT 文は、
法律学科4年、北海道の男性を追加するための SQL 文である。この SQL 文は、「学生データ
の新規作成」という機能で必要になる。
7.データベース作成用DDL
php_customers_db データベースと、内部のテーブル、そして初期データをインサートす
るためのDDLを mysql クライアントからまとめて実行することで、データベースの準備
が整う。実行できたら、データベースは初期化される。
◆DDL
SQL には DDL(Data Definition Language:データ定義言語)という命令群がある。
「テ
ーブルや、テーブルよりも大きな管理単位」を扱う命令群がDDLである。これは、
「名前」
のカラムは文字列で、
「学年」のカラムは整数であるといった、DB に格納データの構造を定
義するための SQL である。
テーブルにデータを格納するには先にデータベースやテーブルができている必要がある
ため、DDLはアプリケーションをサーバに仕込む段階で使うことになる。
34
3.アプリケーション詳細
ページ設計の方針は、フォームの使い方と処理内容を考えて決定する。例えば、フォー
ムで必要項目を繰り返し入力する場合は、同じスクリプトを用いる。
具体的には、フォームに入力された学生データのうち必要データが欠落していた場合、
再度入力を促す必要がある。このような場合、必要項目がすべて揃うまではページを遷移
させず、同じページを繰り返し呼び出す。そのため、1つのスクリプトで入力(編集)、確
認の表示、確定といった複数の処理を行う。
最後にデータが確定すると、そのデータに対する処理は大きく異なる。例えば学生デー
タが確定したときの処理は、DB への書き込みになる。必要項目をチェックしたりデータを
表示するだけの処理と、DB への書き込みとでは、全く異なる処理だ。このように大きく処
理内容が異なる場合は、別のスクリプトにリクエストを出して処理を継続する。
1.各画面とスクリプトの相関図
35
1-1.スクリプト一覧
ファイル名
解説
db_init.php
データベースアクセス用の共通スクリプト
delete.php
学生削除画面のスクリプト
editStudent.php
searchStudent.php
学生の新規登録/編集用画面のスクリプト
学生検索画面のスクリプト
exportCsv.php
CSV ファイルエクスポート用のスクリプト
importCsv.php
CSV ファイルインポート画面のスクリプト
mailInput.php
メール作成画面のスクリプト
sendjob.php
メール送信用のスクリプト
各ファイルとも RDBMS にアクセスするが、すべてのスクリプトに RDBMS のユーザとパス
ワードを記述するのは無駄なうえ危険である。そこで、RDBMS に接続して初期化するところ
までの、最低限の共有が必要な部分だけを別ファイルにする。
36
2.共有ファイル(db_init.php)
このファイルは RDBMS の初期化を記述した、最低限の共有ファイルである。データベー
スへの接続を行い、その接続を保持する$db というリソース変数を作成する。また、その接
続を利用して、RDBMS 操作上の文字エンコーディングを SJIS に設定する。
これにより、このファイルを利用するスクリプトでは、すぐに$db という変数でデータベ
ースが利用できる。アクセス API は PDO。スクリプトからは SJIS でデータのやり取りを行
う。
db_init.php
<?php
$dsn = 'mysql:host=localhost;dbname=php_customers_db';
$db = new PDO($dsn, 'php', 'password');
$db->query("SET NAMES 'SJIS'");
?>
PDO で MySQL にアクセスする方針で、まず PDO 用の DSN を用意。続けて、new 演算子で PDO
オブジェクトを作成する。ここで作成したオブジェクトは$db という変数に格納しているが、
このファイルを読み込む他のスクリプトでは、
すべて$db を介して MySQL にアクセスできる。
このファイルを読み込みさえすれば、他のスクリプトからもここで定義した変数$db が利用
できる。逆に他のスクリプトでは$db という変数を誤って使わないように注意する。
ここでは、MySQL の動作モードを「SJIS」に設定している。
このスクリプトを他の PHP スクリプトから利用する場合は、、次のような文を使って読み
込む。
require_once ‘db_init.php’;
このように外部のスクリプトから読み込まれる可能性のあるスクリプトファイルは、ス
クリプト以外の記述を一切しないように注意する。ダウンロードスクリプトのように、HTML
以外のコンテンツを送信するスクリプトの場合、仮に HTML のコメントや、単なる改行文字
1つであっても、動作エラーを引き起こす可能性がある。エラーにならなくても、ダウン
ロードするファイルの中にゴミが混入することになる。
37
3.学生新規作成(editStudent.php)
このスクリプトはパラメータとして学生 ID を受け取り、その ID の学生データをフォー
ムに読み込んで表示する。学生 ID を受け取ったものの、そのデータが DB に無ければエラ
ーとする。学生 ID を受け取らなければ、学生データの新規作成と見なす。
学生のデータはユーザがフォームのテキストフィールドに入力するが、外部テーブルを
参照する学科、学年、性別、都道府県はデータの選択肢が決まっているので、リストボッ
クスにする。そのため、リストボックスに全選択肢を表示するために、これらのテーブル
からあらかじめ全項目を読み込む必要がある。
フォームに入力したデータは「送信」のクリックでサーバに送信されるが、受け取った
らすぐに DB に書き込まず、一度内容の確認をするようにする。この段階で必要項目にデー
タが入っていない場合などはエラーと見なし、再入力用のフォームを表示する。
エラーがない場合は、確認画面が表示される。ここで再度「送信」をクリックすると、
DB に書き込む。送信されたデータが学生 ID を持っている場合は既存データの更新で、学生
ID を持っていない場合は新規レコードの挿入になる。
editStudent.php
<?php
require_once 'db_init.php';
require_once 'HTML/QuickForm.php';
$q = $db->query('SELECT id,name FROM kas ORDER BY id ASC ');
$kasMaster = array();
$kasMaster[-1] = '選択(゜Д゜)エ―!?';
while ($row = $q->fetch())
{
$kasMaster[$row['id']] = $row['name'];
}
(中略)
$q = $db->query('SELECT id,name FROM genders ORDER BY id ASC ');
$gendersMaster = array();
$gendersMaster[-1] = 'せ、選択("б")';
while ($row = $q->fetch())
{
$gendersMaster[$row['id']] = $row['name'];
}
38
$theCustomer = array();
if (!empty($_GET['id']))
{
$sql = 'SELECT * FROM customers WHERE id=:c_id ORDER BY id ASC ';
$stmt = $db->prepare($sql);
$stmt->bindParam(':c_id', $_GET['id']);
$stmt->execute();
if ($row = $stmt->fetch())
{
$theCustomer = $row;
}
}
(中略)
if (!empty($_GET['id']))
{
$form->setDefaults(
array(
'id'=>$theCustomer['id'], 'studentnum'=>$theCustomer['studentnum'],
'name'=>$theCustomer['name'], 'mail'=>$theCustomer['mail'],
'phone'=>$theCustomer['phone'],
)
);
$kasSelect->setSelected($theCustomer['ka']);
$nensSelect->setSelected($theCustomer['nen']);
$gendersSelect->setSelected($theCustomer['gender']);
$prefsSelect->setSelected($theCustomer['pref']);
}
else
{
$form->setDefaults(
array(
'id'=>''
)
);
}
(後略)
39
スクリプトでは、まず初めにテーブル kas、nens、genders、prefs のマスターデータを
すべて取得した後、学科や学年、性別、都道府県のリストボックスに一覧を表示するため
の配列を作成している(赤文字)。なお、リストボックスに表示する都合上、キーが-1の
先頭要素として「選択~」という項目を最初に追加した。
$kasMaster、$nensMaster、$prefsMaster、$gendersMaster の読み込みが完了したら、学
生が指定されているかどうか判定し、指定されていたらその学生の情報を取得する。学生
が指定されている場合は、変数$theCustomer にデータを保持しておく。特定の学生が何学
科か、何学年か、男性か女性か、また都道府県を表示するための目的ならば、それは SQL
でテーブル kas、nens、genders、prefs を JOIN して取得するほうが合理的で、ここでもそ
うしている(青文字)。その次にフォームを定義している。データ入力に用いるフォームな
ので入力内容の確認、バリデーションなどのルール、再入力の判断などが重要なので、
QuickForm を使用した。フォームが持つ要素は次の通りである。
id
:学生を識別する値(指定された場合のみ)
studentnum :学生の学籍番号の文字列
name
:学生の名前の文字列
kas
:学科を1つ選択するリストボックス
nens
:学年を1つ選択するリストボックス
genders
:性別を1つ選択するリストボックス
prefs
:都道府県を1つ選択するリストボックス
mail
:メールアドレスを入力する文字列
phone
:電話番号を入力する文字列
上記のうち kas、nens、genders、prefs については、参照渡しで別の変数に代入してい
る。これは後の記述を簡略化するためである。
また学籍番号、名前、メールアドレスは必須項目とし、文字列を入力するフィールドに
は trim()関数によるフィルタを適用した。
フォームが構築できた後に、このフォームの初期値を動的に設定している。学生 ID が送
信されているときは、その学生のデータでフォームの各フィールドを埋める。この初期値
は、フォームの setDefaults()関数で設定する値なので、すでに送信されたデータがあると
き、つまり再入力などの局面では使われない(オレンジ文字)。
ここまでで HTML 出力の準備がすべて整う。あとはこれらのデータに基づいて動的に HTML
を出力するだけである。
HTML の中では、フォームのバリデーションを行って、そのフォームを処理する。バリデ
ーションエラーの場合は、フォームが再入力を必要とするケースなので、フォームのまま
表示する。
40
4.学生検索(searchStudent.php)
このフォームは DB 中のデータに影響を与えるものではない。条件を変えて繰り返し検索
することができるようにする。そのため、フォームと検索結果を同じページに表示するこ
とにする。このスクリプトの趣旨は、あくまでも学生を検索して表示することである。
検索結果は、学生に対する様々な処理の入り口となる。まず、絞り込まれた学生に対し
て同報メールが送信できるようにする。また、各学生のデータを削除するときも、この表
示から行えるようにする。更に、各学生のデータを editStudent.php で編集するときも、
この表示を入り口とする。
もう1つ、データのエクスポート機能は全ユーザのデータを無条件でエクスポートする
より、検索で絞り込まれた学生だけをエクスポートできるほうが柔軟性が高いため、この
画面から行えるようにする。全データをエクスポートしたいときは、条件をつけずに検索
すればよい。
searchStudent.php
<?php
require_once 'db_init.php';
require_once 'HTML/QuickForm.php';
(途中省略)
$sql = "SELECT c.id AS c_id,c.studentnum AS c_studentnum,c.name AS c_name,a.name AS
a_name,n.name AS n_name,g.name AS g_name,p.name AS p_name,c.mail AS c_mail,c.phone
AS c_phone " .
"FROM customers AS c,kas AS a,nens AS n,prefs AS p,genders AS g " .
"WHERE c.ka=a.id AND c.nen=n.id AND c.pref=p.id AND c.gender=g.id " .
"ORDER BY c.id ASC ";
$studentnum = $form->exportValue('studentnum');
$kas = $form->exportValue('kas');
$nens = $form->exportValue('nens');
$name = $form->exportValue('name');
$genders = $form->exportValue('genders');
$prefs = $form->exportValue('prefs');
$mail = $form->exportValue('mail');
$phone = $form->exportValue('phone');
if ( !empty($studentnum) ||
(!empty($kas) && -1 != $kas) ||
41
(!empty($nens) && -1 != $nens) ||
!empty($name) ||
(!empty($genders) && -1 != $genders) ||
(!empty($prefs) && -1 != $prefs) ||
!empty($mail) ||
!empty($phone))
{
$sql = "SELECT c.id AS c_id,c.studentnum AS c_studentnum,c.name AS c_name,a.name
AS a_name,n.name AS n_name,g.name AS g_name,p.name AS p_name,c.mail AS c_mail,c.phone
AS c_phone " .
"FROM customers AS c,kas AS a,nens AS n,prefs AS p,genders AS g " .
"WHERE c.ka=a.id AND c.nen=n.id AND c.pref=p.id AND c.gender=g.id AND " .
"(c.studentnum LIKE :c_studentnum OR c.ka=:c_ka OR c.nen=:c_nen OR c.name
LIKE :c_name OR c.gender=:c_gender OR c.pref=:c_pref OR c.mail LIKE :c_mail OR c.phone
LIKE :c_phone) " .
"ORDER BY c.id ASC ";
}
$stmt = $db->prepare($sql);
if ( !empty($studentnum) ||
(!empty($kas) && -1 != $kas) ||
(!empty($nens) && -1 != $nens) ||
!empty($name) ||
(!empty($genders) && -1 != $genders) ||
(!empty($prefs) && -1 != $prefs) ||
!empty($mail) ||
!empty($phone))
{
$studentnum_l = '';
if (!empty($studentnum))
{
$studentnum_l = '%' . $studentnum . '%';
}
$name_l = '';
if (!empty($name))
{
42
$name_l = '%' . $name . '%';
}
$mail_l = '';
if (!empty($mail))
{
$mail_l = '%' . $mail . '%';
}
$phone_l = '';
if (!empty($phone))
{
$phone_l = '%' . $phone . '%';
}
$stmt->bindParam(':c_studentnum',
$stmt->bindParam(':c_ka',
$stmt->bindParam(':c_nen',
$stmt->bindParam(':c_name',
$studentnum_l);
$kas_l);
$nens_l);
$name_l);
$stmt->bindParam(':c_gender',
$genders);
$stmt->bindParam(':c_pref',
$prefs);
$stmt->bindParam(':c_mail',
$mail_l);
$stmt->bindParam(':c_phone',
$phone_l);
}
$stmt->execute();
(途中省略)
echo '<DIV>';
$form->display();
echo '</DIV>';
(途中省略)
{
$ids[] = 'ids[]=' . $row['c_id'];
$action =
'<A href="editStudent.php?id=' . $row['c_id'] . '">編</A>' . '&nbsp;' .
'<A href="delete.php?id=' . $row['c_id'] . '">削</A>' . '&nbsp;' .
'<INPUT type="checkbox" name="mailTo[]" value="' . $row['c_id'] . '"
checked=\"on\" />M&nbsp;';
?>
43
(途中省略)
</FORM>
<a href="exportCsv.php?<?= implode('&', $ids) ?>">CSV エクスポート</A>
</BODY>
このスクリプトは、冒頭の処理は editStudent.php とまったく同じである。
QuickForm でのフォームの使い方で大きく異なる1点は、検索画面で使うフォームは、入
力内容の確認はしない上(フォーム入力が間違っていれば再度検索)
、検索が済めばそのデ
ータは使わないので毎回ほぼ使い捨てのデータとなる点だ。基本的に繰り返して検索する
ためのフォームである。バリデーションを行わないので、バリデーションルールを持たな
い。
フォームを構成したら、その段階で送信されているデータはフォームオブジェクトの要
素に格納されているので、すぐに exportValue()関数で値の取得を行う(緑文字)。
検索条件が何も指定されていないのなら、ここでは全件表示する(茶文字)。
フォームの送信データが何もないケースとそうでない(1つでも指定されている)ケー
スで if 文を用いて分岐して SQL 文を作っている(赤文字)
。
SQL は、文字列の検索あ LIKE 演算子を用いる。そのため、これらのフィールドにはフォ
ームで送信されたデータの前後に「%」記号を追加して、ワイルドカード検索を可能にす
る。これにより、「大」で検索すれば SQL 文では「%大%」になり、「大澤」も「大野」も
見つかる。
SQL を構築してから、prepare()関数でステートメントを作る(黄文字)。
その後に青文字部分で全プレースホルダにパラメータを設定していく。初回表示の場合
は絞り込みのための条件がないので、プレースホルダもパラメータも使わない。SQL 文が構
築できたら、実行して検索結果を取得しておく。
これで HTML 出力の準備が整う。
この HTML には大きく分けて「検索フォーム」と「検索結果テーブル」の2つがある。
検索結果テーブルは、検索によって絞り込まれた学生の一覧を、テーブル形式で表示す
るものである。1行1学生で、「編集」や「削除」のための機能リンクも表示する。また、
メール送信が、チェックボックスにより任意で送る相手を選択できる。
メール送信のための操作はフォーム送信による操作、つまりボタンをクリックして POST
する、通常のフォームである。
CSV エクスポートのリンクも作成した。リンクをクリックすればそのとき表示されている
学生のデータをすべてダウンロードする。
後は HTML を出力するだけである。まず最初に検索フォームを出力する。これは display()
関数を呼び出せばよい(ピンク文字)。
次に、検索結果をテーブルに出力する。中身はループで出力する。
44
紫文字部分の
$ids[] = 'ids[]=' . $row['c_id']; 。これは、構文からは配列変数の
$ids に文字列要素を追加していく処理である。
$row はこのループで使っている行データ、つまり DB の検索結果の行を保持する配列であ
る。その「c_id」カラムは、学生 ID のカラムだ。つまり、ここでは例えば学生 ID が1の
場合は、[ids[]=1]という文字列を作っている。
これは、配列として HTTP パラメータを PHP エンジンに受け取らせる方法を利用している。
ここでは、CSV ダウンロードの学生 ID を配列で渡すためにこの形式を利用している(オ
レンジ文字)
。
あとは HTML の出力処理だが、先に記したループの中で、SQL の実行結果である配列$row
を用いて変数$action に処理リンクを作りこんでいる(黄緑文字)。これは各行の右端に表
示するリンクとチェックボックスである。
5.学生削除(delete.php)
このスクリプトはパラメータとして学生 ID を受け取る。遷移元は searchStudent.php で
ある。検索した結果から、その1件分の学生 ID をパラメータとして、このスクリプトを呼
び出す。
delete.php
<?php
require_once 'db_init.php';
require_once 'HTML/QuickForm.php';
(中略)
if (!empty($_GET['id']))
{
$form->setDefaults(
array(
'id'=>$theCustomer['id'],
'studentnum'=>$theCustomer['studentnum'],
'name'=>$theCustomer['name'],
'mail'=>$theCustomer['mail'],
'phone'=>$theCustomer['phone'],
)
);
$kasSelect->setSelected($theCustomer['ka']);
45
$nensSelect->setSelected($theCustomer['nen']);
$gendersSelect->setSelected($theCustomer['gender']);
$prefsSelect->setSelected($theCustomer['pref']);
}
(中略)
if ($form->getSubmitValue('Status') != 'in-confirm-phase')
{
$form->addElement('hidden', 'Status', 'in-confirm-phase');
$form->freeze();
echo '<b>以下の学生を削除します</b>';
(中略)
else
{
$idV = $form->exportValue('id');
if (!empty($idV))
{
$sql = "DELETE FROM customers WHERE id=:id ";
$stmt = $db->prepare($sql);
$stmt->bindParam(':id', $form->exportValue('id'));
if (FALSE !== $stmt->execute())
{
echo '<b>' . $form->exportValue('name') . 'を削除しました</b>';
}
else
{
echo '<b>FAIL:削除に失敗しました</b>';
}
}
else
{
echo '<b>customer ID が指定されていません</b>';
}
}
(後略)
46
このスクリプトでは、学生 ID を受け取って削除するだけである。削除をいきなり実行し
ないように、ここでも QuickForm を用いて確認画面を作成し、学生の新規作成画面や検索
画面とフォーマットを統一する。このフォームは確認表示をする目的でのみ使う。そのた
め、いきなり freeze()関数を呼び出すことになる。
DB の初期化を行った後は、編集と検索で QuickForm を使ったときと同様に、マスターデ
ータの読み込みとフォームの定義を行う。バリデーションも必要ない(DB から読み込むの
ですでにバリデーション済み)ため、バリデーションルールは不要で、validate()関数の
実行もしない。
次に、setDefaults()関数および setSelected()関数で初期値を与える(赤文字)。これに
より、初回表示の段階で、DB から読み込んだ学生データが表示されるようにする。学生 ID
が指定されていないときはすべてのフィールドが空になる。
フォームに値が設定できたら、これで HTML の出力準備は完了する。
処理ではまず’Status’要素の確認を行う。これは、確認フェーズとトランザクション
フェーズの区別をしている。一度ユーザに確認してもらうために表示するのが確認フェー
ズで、それを了解して「送信」をクリックしたときにトランザクションフェーズとする。
確認フェーズでは、確認用にフォームを表示するだけなので、フォームを freeze()関数
で凍結する。その際、フォームの隠しフィールドに’Status’要素を追加する(青文字)。
これは先の QuickForm の使い方とは違い、バリデーションもせずに内容を凍結して、こ
こでしか表示しない。表示して確認してもらうことのみが目的なので、freeze()して
display()するだけである。
一方、確認フェーズのフォームを受信した場合、つまり上記の if 文に対応する else 文
のブロックでは、実際に RDBMS から行を削除する処理になる(オレンジ文字)。
47
6.メール入力(mailInput.php)
こ の ス ク リ プ ト は パ ラ メ ー タ と し て 学 生 ID を 配 列 で 受 け 取 る 。 遷 移 先 は
searchStudent.php だが、絞り込まれた複数の学生に一括送信するため、学生 ID を複数受
け取る必要がある。searchStudent.php で学生リストすべてにチェックボックスを表示し、
デフォルトでチェックを入れた状態からチェックを外すというようにした。これで、メー
ルを送らない学生をピックアップできる。
その他に、メール編集用のフォームも持つ。メールのタイトルとなる Subject と、メー
ルの本文を入力する。
本文を書いてから外したい学生が発生することを想定して、この画面でも学生にチェッ
クボックスをつけた。
mailInput.php
<?php
require_once 'db_init.php';
$ids = $_POST['mailTo'];
if (0 < count($ids))
{
$in = 'IN (' . implode(',', $ids) . ') ';
$sql = 'SELECT c.id AS c_id,c.studentnum AS c_studentnum,c.name AS c_name,a.name
AS a_name,n.name AS n_name,g.name AS g_name,' .
'p.name AS p_name,c.mail AS c_mail,c.phone AS c_phone ' .
'FROM customers AS c,kas AS a,nens AS n,prefs AS p,genders AS g ' .
'WHERE c.ka=a.id AND c.nen=n.id AND c.pref=p.id AND c.gender=g.id AND c.id
' . $in .
'ORDER BY c.id ASC ';
$stmt = $db->prepare($sql);
$stmt->execute();
}
(中略)
<?php
while ($row = $stmt->fetch())
{
$action =
'<INPUT type="checkbox" name="mailTo[]" value="' . $row['c_id'] . '"
48
checked=\"on\" />M&nbsp;';
?>
<TR>
<TD><a
href="editStudent.php?id=<?=
$row['c_id']
?>"><?=
$row['c_id'] ?></A></TD>
<TD><?= $row['c_studentnum'] ?></TD>
<TD><?= $row['a_name'] ?></TD>
<TD><?= $row['n_name'] ?></TD>
<TD><?= $row['c_name'] ?></TD>
<TD><?= $row['g_name'] ?></TD>
<TD><?= $row['p_name'] ?></TD>
<TD><?= $row['c_mail'] ?></TD>
<TD><?= $row['c_phone'] ?></TD>
<TD><?= $action ?></TD>
</TR>
<?php
}
?>
(後略)
このスクリプトは QuickForm で作られたフォームデータを受け取らないため、QuickForm
を 使 わ な い 。 メ ー ル 送 信 先 の 学 生 ID を 配 列 で 受 け 取 る 。 送 信 し た ス ク リ プ ト
(searchStudent.php)を確認すると、学生 ID を「mailTo[]」キーで渡す。つまり、$_POST
の「mailTo」キーで取得できる(赤文字)。
これが存在することを確認した上で、学生データを表示するための SQL 文を作る(青文
字)。これは searchStudent.php で表示したような学生テーブルを表示するための作業であ
る。
ループ(緑文字)は searchStudent.php と似ているが、学生の編集と削除のためのリン
クは表示しない。
ループで学生テーブルを表示し終えると、その次にメール入力のためのフォームを表示
する。ここでは、メールのタイトルとなる Subject フィールドと、メールの本文を入力す
るフィールドがある。また、同報なので、宛て先を CC にするか選択できる。
49
7.メール送信(sendJob.php)
このスクリプトはパラメータとして mailInput.php で作成したフォームを受け取る。そ
の内容は、学生 ID の配列と、メールの内容などのデータである。
このスクリプトでは、まず送信されたメールの内容に対して、メールに適合したエンコ
ードを施す。次に、学生 ID の配列で DB を検索してメールアドレスの一覧を取得する。最
後に、それぞれの学生に対して準備したメールを送信する。
失敗時は、成功件数と失敗件数を表示する。
sendJob.php
<?php
require_once 'db_init.php';
define('NL', "\r\n");
//CR+LF
define('MYNAME', 'おおさわゆたか');
//送信者の名前
define('MYADDR', '[email protected]'); //送信者のメールアドレス
(中略)
$stmt->execute();
$addresses = array();
while ($row = $stmt->fetch())
{
$addresses[] = $row;
}
}
(中略)
function mmhdr4subject($text)
{
$top = 0;
while ($top < mb_strLen($text))
{
$cut = mb_subStr($text, $top, 10);
$array[] = mmhdr($cut);
$top += 10;
}
return implode(NL. ' ', $array);
}
50
(中略)
if ('on' == $_POST['useCc'])
{
foreach ($addresses as $entry)
{
$cc .= 'Cc: ' . mmhdr($entry['c_name']) . '<' . $entry['c_mail'] . '>' .
NL;
}
}
(中略)
$tSubject = mmhdr4subject($subject);
$fromAddr = MYADDR;
$from = mmhdr(MYNAME) . '<' . $fromAddr . '>';
$fixedHeader = 'From: ' . $from . NL .
'Reply-To: ' . $from . NL .
'Content-Type: text/plain; charset=ISO-2022-JP' . NL .
'X-Mailer: ' . mmhdr('フォームメール') . 'PHP/' .
phpVersion();
$successCount = 0;
$errorCount = 0;
foreach ($addresses as $entry)
{
$toAddr = $entry['c_mail'];
$to = mmhdr($entry['c_name']) . '<' . $toAddr . '>';
$headers = 'To: ' . $to . NL .
$cc .
$fixedHeader;
if (mail($toAddr, $tSubject, $tMessage, $headers))
{
++$successCount;
}
else
{
++$errorCount;
}
51
このスクリプトは、学生 ID を配列で受け取る。また、メールの内容に関するフィールド
も含まれる。すべてフォームの POST で受け取るので、配列の$_POST 変数で取得できる。
まず初めに DB アクセスの準備をしてから、メールの送信者に関する定数を準備する(赤
文字)。
これらの定数は、NL がメール送信プロトコル(SMTP)の改行文字、MYADDR がメール送信
者のメールアドレスである。MYNAME はメール送信者を日本語表示するためのもので、内容
は任意。
MYADDR と MYNAME は使用者の環境で適宜修正してから使うこと。
次に、宛て先の学生を DB から取得するための SQL 文を作成する。これが済めば、すぐに
実行して配列に取得する。ここではメールアドレスと表示名だけなので、全部配列にコピ
ーしている(青文字)。
宛て先の一覧が配列変数$addresses に取得できたら、メールの送信に移る。まず、メー
ル送信に使う独自関数を2つ用意した。これは日本語を含むメールヘッダを MIME ヘッダ形
式でエンコードするためのものである。ここでは SJIS で内部処理している文字列を
JIS(ISO-2022-JP)形式に変換してから MIME エンコードするようにしている。
PHPの日本語メール送信関数には問題があり、メールのタイトル(Subject)が正常に
送信できなかったり途中から文字化けが発生したりする。そのためここでは、長い日本語
のヘッダを正しく MIME エンコードできないため、Subject は自前で MIME ヘッダエンコード
行う(mb_send_mail()関数を使う必要がない(使えない))
。それが mmhdr4subject()関数で
ある(緑文字)。Subject に関してのみ mmhdr4subject()関数内部で 10 文字ずつに分割し、
そのそれぞれに対して mmhdr()関数を用いて MIME エンコードを行う。
同報の宛て先に CC を使う場合は、CC 用のヘッダを作る必要がある。学生の全員をループ
して「Cc:」ヘッダを変数$cc に構築する(オレンジ文字)。
$entry は RDBMS から読み出した学生データを保持する配列である。表示名(c_name)と
メールアドレス(c_mail)を適切にエンコードする。
メール本文の文字エンコーディングを「Content-Type:」ヘッダにつける必要があるため、
次にメール本文の文字エンコード変換を行う。そして、先に記した mmhdr4subject()関数を
使って Subject も MIME エンコードする(黄文字)
。
次に「From:」ヘッダを作る。これは「Cc:」ヘッダと同様の方法でエンコードする(紫
文字)。
黄緑文字部分では、最終的なヘッダを作成する。「Content-Type:」ヘッダには、先ほど
メール本文のエンコードを変換した方法に従って作成する。「X-Mailer:」ヘッダは念のた
めつけておく。
以上で、全メールに共通するヘッダとメール本文の準備は完了。あとは各学生に対して
ループで送信を実行する(ピンク文字)。
すでに作成したヘッダ($cc と$fixedHeader)に、各学生の宛先メールアドレスだけを
52
追加してヘッダを完成させ、mail()関数で送信する。
ループが完了すると、最後にその結果を表示してスクリプトを終了する。
8.CSV エクスポート(exportCsv.php)
このスクリプトの形態はダウンロードスクリプトとなる。このスクリプトは画面を何も
表示せず、単にダウンロードさせるだけの処理である。このスクリプトではパラメータと
して学生 ID を配列で受け取る。各学生のデータを RDBMS から読み取り、CSV 形式にしてフ
ァイルに一度保存する。パラメータで受け取った学生のデータをすべて CSV ファイルに保
存できたら、そのファイルをダウンロードさせる。
最後に、保存した CSV ファイルをハードディスクから削除しておく。
exportCsv.php
<?PHP
require_once 'db_init.php';
(中略)
if (FALSE !== $stmt->execute())
{
while ($row = $stmt->fetch())
{
$line = array();
$line[] = $row['c_studentnum'];
$line[] = $row['a_name'];
$line[] = $row['n_name'];
$line[] = $row['c_name'];
$line[] = $row['g_name'];
$line[] = $row['p_name'];
$line[] = $row['c_mail'];
$line[] = $row['c_phone'];
$rows[] = $line;
}
}
else
(中略)
53
define('FILENAME', 'customers-csv.txt');
if (FALSE !== $fp = @fopen(FILENAME, 'w'))
{
foreach ($rows AS $line)
{
@fputCsv($fp, $line);
}
@fclose($fp);
}
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . FILENAME . '"');
(後略)
注意点として、スクリプトの実行に際してレスポンス以外の内容を出力しないようにす
ること。そのため、PHP関数には@演算子をつけておき、エラーメッセージをレスポンス
内に出力しないように制御する。
生成する CSV は、まずファイルとして作成する。HTTP パラメータとして受け取った学生
ID の配列を RDBMS から取得して、各学生を1つの配列にする。そして、fputCsv()関数を使
ってファイルに書き出す。
ここでは、SQL による取得とファイルへの書き出しを分離したかったので、各学生の配列
($line)をさらに複数格納した配列($rows)を作った(赤文字)。
この二重配列が無事に作成できたら、ファイル名「customers-Csv.txt」を FILENAME 定
数として書き出しモードでオープンし、fputCsv()関数で1行ごとに出力する(青文字)。
各関数に@演算子をつけてエラー出力を制御している。
これで FILENAME というファイルができているので、これをダウンロードさせる(緑文字)。
最後に、unlink()関数でこのファイルを削除してスクリプトを終了する。
54
9.CSV インポート(importCsv.php)
CSV ファイルをクライアント PC から読み込ませる処理。ここでは、カンマ区切りの形態
でファイルをアップロードする。
学生 ID は DB 側で自動連番になっているため必要ない。また文字列を二重引用符で囲む
必要もない。
ファイルをアップロードするときは、アップロード用のフォームと、その受信スクリプ
トを用意する。ここでは送信用フォームと受信用スクリプトを同じスクリプトにする。フ
ァイル名が指定されていたらファイル処理を行い、ファイル名が指定されずに送信された
らメッセージを表示したうえで再入力を促す。
このとき、アップロードするファイルはテキストファイルなので、文字エンコーディン
グ(Shift_JIS や UTF-8 など)が何であるかを仮定することができない。
ファイルの文字エンコーディングはユーザが指定する。
インポート処理そのものは、アップロードされた CSV ファイルを開いて DB 挿入用の SQL
を組み立て、その SQL を実行するだけである。順に挿入してファイルの末尾に達したら、
最後はそのファイルを削除する。
importCsv.php
<?php
require_once 'db_init.php';
require_once 'HTML/QuickForm.php';
$q = $db->query('SELECT id,name FROM kas ORDER BY id ASC ');
$kasMaster = array();
$kasMaster[-1] = '選択してください';
while ($row = $q->fetch())
{
$kasMaster[$row['id']] = $row['name'];
}
(中略)
$q = $db->query('SELECT id,name FROM genders ORDER BY id ASC ');
$gendersMaster = array();
$gendersMaster[-1] = '選択してください';
while ($row = $q->fetch())
55
{
$gendersMaster[$row['id']] = $row['name'];
}
(中略)
$form->addElement('text', 'encoding',
'文字エンコーディング');
$fileUpload = &$form->addElement('file', 'csvFile',
'CSV ファイル');
$form->addRule('csvFile', 'いやいや、ここはアップしようよ(^^;)', 'uploadedfile');
$form->setRequiredNote('<SPAN style="color: #ff0000;">*</SPAN>は必須項目です');
function myProcess($values)
{
global $fileUpload;
global $form;
$uploadPath = realPath(".");
if ($fileUpload->isUploadedFile())
{
$fileUpload->moveUploadedFile($uploadPath, FILENAME);
}
}
(中略)
if ($form->validate())
{
$form->process('myProcess', true);
$encoding = $form->exportValue('encoding');
$sql = "INSERT INTO customers (studentnum,ka,nen,name,gender,pref,mail,phone)
" .
"VALUES (:studentnum,:ka,:nen,:name,:gender,:pref,:mail,:phone) ";
$stmt = $db->prepare($sql);
(後略)
CSV ファイルをインポートする処理は、ファイルのアップロードから始まる。
このスクリプトは、まず DB の初期化とログの準備をしてから kas,nens,genders,prefs
のマスターテーブルを読み込む(赤文字)。ここでマスターを読み込むのは、CSV には、性
別として genders の主キーが保存されているわけではなく、
「男性」か「女性」の文字列で
56
格納されている。これを DB 中では genders の主キーで保存しなければならない。そのため、
文字列から主キーへの変換を行うためのマスターデータとして読み込んでおく。
次にフォームを定義する。これはファイルアップロードとアップロードする CSV ファイ
ルの文字エンコーディングを指定するためのフォームである。ファイルアップロードの要
素は次のように追加している。要素タイプが「file」で、要素の名前は「csvFile」である
(青文字)。
$fileUpload = &$form->addElement('file', 'csvFile',
'CSV ファイル');
この csvFile 要素には、必須としてバリデーションルールを指定した。ファイルアップ
ロードの必須指定は「required」ではなく「uploadedfile」と指定する(オレンジ文字)。
ここでは、一時ファイルからカレントディレクトリ(スクリプトのあるディレクトリ)
に固定のファイル名でコピーしてくる処理とした。それが、myProcess()関数である(紫文
字)。
このユーザ関数に渡される引数は、フォームの送信データ($_POST のデータ)一式であ
る。それだけしか渡されないため、それ以外の変数にアクセスするための global 指定が必
要である。ファイルアップロード用のフォーム要素が$fileUpload なので、最初にこれをグ
ローバル宣言する。
次に、コピー先のディレクトリを絶対パスで解決している。
その次に、isUploadedFile()関数でファイルがアップロードされたことを確認している。
この関数はファイルアップロード用の要素($fileUpload)が持つ関数で、ファイルがアッ
プロードされている場合は TRUE、それ以外の場合は FALSE を返す。
その次に、同じく$fileUpload が持つ moveUploadedFile()関数を呼び出す。これはアッ
プロードされたファイルを引数のディレクトリにコピーする関数である。ここではアップ
ロードするファイルが1つだけなので、ファイル名も指定して呼び出している。
以上で、インポート処理を行うための準備が完了する。
HTML セクションを見ると、まず初めにバリデーションを行う。これで必須とされたファ
イルアップロードが行われたかどうかのチェックをする。ファイルがアップロードされて
いない場合はバリデーションに失敗(FALSE を返す)するので、if 文に入らない。
If 文の中ではファイルがアップロードされたときの処理を行う。前述のように、確認フ
ェーズを用いず、すぐにトランザクションを実行する。
初めに、フォームの process()関数を呼び出す(黄文字)。これはユーザ定義のフォーム
処理関数を呼び出す関数で、第1引数にユーザ定義の処理関数(先に定義した myProcess()
関数)の名前を指定し、第2引数には「ユーザ関数にファイルアップロー情報も渡すかど
うか」を BOOL で指定する。ここでは TRUE を指定する。これで、ユーザ関数が実行され、
一時ディレクトリからカレントディレクトリにアップロードファイルがコピーされる。
57
ユーザ関数を実行した後は、ループで CSV ファイルの各行を学生として DB に追加してい
く処理で、これがインポート処理の実体である。
まず、ループに入る前に、アップロードされたファイルの文字エンコーディングを取得
し、次に挿入用の SQL 文を作ってステートメントを取得しておく(黄緑文字)。
あとは、ファイルを開いて各行ごとの納入を実行する。その際、文字エンコーディング
が指定されている場合には、行ごとに文字エンコーディングの変換を行う。内部も字エン
コーディングは SJIS である。この処理がないと、アップロードしたファイルが文字化けし
た状態でインポートされることがある。
SQL にはプレースホルダにパラメータを指定していく必要があるが、このとき学科、学年、
性別、都道府県に関しては、表示文字列から主キー値に変換する必要がある。
最後に、アップロードされたファイルを削除して処理を終了する。
58
4.改善点、反省
このアプリケーションではフォームを自分に送るケースで QuickForm を使用したが、
QuickForm には画面デザインが制約を受けること、ファイルのアップロードが使いにくい
問題がある(QuickForm では確認表示のためにフォームを freeze()するが、現状では freeze()
してもファイル入力のためのフィールドは編集可能な状態で表示されてしまう。もちろん、
この画面でファイルがアップロードできてしまう)。
目的達成後に、次のページへの遷移を別途考える必要がある。
フォームから戻る方法を考える必要がある。
ここでは QuickForm を用いて「自分に送信」を多用したが、自分に送信すると、トラン
ザクションフェーズ終了後に「完了」を表示するだけだとユーザに対しては不親切である。
検索画面の学生テーブルのデータ量が増えると、下に冗長なテーブルになってしまうの
で、ページネーション、PAGER 等で対応する必要がある。
検索結果表示のバリエーションがないことが不便である。id 順、学籍番号順、学科順五
十音順など、表示条件の多彩な選択肢を設けることが望ましい。
このアプリケーションは利用者を1名と想定している。つまり、この Web サイトにアク
セスできる者はすべて学生データを見ることができてしまう。昨今の傾向からすると、デ
ータの扱いとしてはセキュリティ上の不安があるので、これは重要な改善点である。
これらの問題は、PHPとその周辺知識の明らかな不足によるものなので、更に知識の
追求をする必要性を感じた。
59
5.謝辞
最後になりますが、これまで学習の指導をしてくださった教授の山本昌弘氏、そして毎
週ゼミに参加し、共に支えあってくれた 10 人の仲間に深く感謝の気持ちを述べたいと思い
ます。今まで大変お世話になりました。ありがとうございました。
6.参考文献
・「基礎からのPHP」
山田和夫
著
SoftBank Creative
・PHPの絵本
(株)アンク
著
翔泳社
・Wikipedia
60