前回のPHPで書いたMVCでは「V」のコード部分はごく小さいview()関数のみでしかもほとんど書き換えすることはなさそうなのでファイル分けしないで「C」と統合させてみました。(viewctl.cgi)
ということで今回は「M」をM.pmに、「V」と「C」をviewctl.cgiにファイル分けしてみました。
処理内容は、メニューを選んで画面遷移していくシンプルなものです。
■「V」+「C」(viewctl.cgi)
#
# filename: viewctl.cgi
#
# memo: 表示(View)と制御(Controller)の機能を受け持つ
#
use strict;
use constant MAX_INPUT=>1024*512; # 最大許可受信バイト
use constant HEADER=>"Content-type: text/html; charset=Shift_JIS\n\n";
use vars qw/%req %file $obj %MENU/; # グローバル
use Switch; # switch文モジュール
use M; $obj= M->new(); # モデルを取り込んでインスタンス化
# メニューのキーと表示順と文字
%MENU= (next1=>{index=>1, text=>"メニュー1"},
next2=>{index=>2, text=>"メニュー2"},
next3=>{index=>3, text=>"メニュー3"});
# urldecode
&decode();
# 全actデフォルト値セット
$obj->{tpl}= $obj->{errtpl}= "./common.tpl";
$obj->{SITENAME}= "MVCで作るCGIテスト";
# actごとのページ切り替え
my $body;
switch ($req{act})
{
case "next1" {
$obj->{TITLE}= "next1のページ";
$obj->{act}= "next2";
$body= $obj->body_next1() or &error;
$obj->{body}= &menu('next1') . $body;
}
case "next2" {
$obj->{TITLE}= "next2のページ";
$obj->{act}= "next3";
$body= $obj->body_next2() or &error;
$obj->{body}= &menu('next2') . $body;
}
case "next3" {
$obj->{TITLE}= "next3のページ";
$obj->{act}= "next1";
$obj->{message}= 'メッセージを表示させてみるテスト';
$body= $obj->body_next3() or &error;
$obj->{body}= &menu('next3') . $body;
}
else {
$obj->{TITLE}= "トップページ";
$obj->{act}= "next1";
$body= $obj->body_toppage() or &error;
$obj->{body}= &menu() . $body;
}
}
# 画面表示
&view();
sub view
{
my $tpl= $obj->{'errmsg'} ? $obj->{'errtpl'} : $obj->{'tpl'};
my $fh= *view;
my $html;
open($fh, $tpl);
read($fh, $html, -s $fh);
close($fh);
$html=~ s/%(\w+)%/$obj->{$1}/g;
$html=~ s/%act%/$obj->{act}/g; # 今回は置換後の文字に%act%が含まれるように手抜きして書いたので
print HEADER,$html;
exit;
}
sub error
{
$_[0] and $obj->seterrmsg($_[0]);
&view();
}
sub entity
{
$_[0]=~ s/&/&/g;
$_[0]=~ s/</</g;
$_[0]=~ s/>/>/g;
$_[0]=~ s/"/"/g;
}
sub menu
{
my $menu;
for (sort{$MENU{$a}->{index} <=> $MENU{$b}->{index}}keys %MENU)
{
&entity($MENU{$_}->{text});
if ($_[0] eq $_){
$menu.= $menu ? " | $MENU{$_}->{text}" : $MENU{$_}->{text};
} else {
my $tmp= "<a href=\"$ENV{SCRIPT_NAME}?act=$_\">$MENU{$_}->{text}</a>";
$menu.= $menu ? " | $tmp" : $tmp;
}
}
return $menu;
}
sub decode
{
my $buf;
if (my $cl=$ENV{CONTENT_LENGTH}) # post
{
if ($cl > MAX_INPUT){ &error("request data size over!"); }
binmode(STDIN); read(STDIN, $buf, $cl);
if ($ENV{CONTENT_TYPE}=~ /boundary="?([^";,]{5,})/) # multipart
{
my $boundary= "--" . $1;
my $nl= "\x0d\x0a";
for (split(/\Q$nl$boundary/, $buf))
{
if (/="([^"]+).*?(?:="([^"]+).*?:\s*([\w\-\.\/]+).*?)?$nl$nl(.*)/s)
{
if ($2 ne '') # file upload
{
$file{$1}{'name'}= $1;
$file{$1}{'filename'}= $2;
$file{$1}{'mime'}= $3;
$file{$1}{'data'}= $4;
} else { $req{$1}= $4; } # else form-data
}
}
$buf= '';
}
}
my($name, $value);
my $qs= $ENV{QUERY_STRING};
$qs=~ tr/+/ /; $buf=~ tr/+/ /;
for (split(/&/, $qs),split(/&/, $buf),split(/;\s*/, $ENV{HTTP_COOKIE}))
{
($name, $value)= split(/=/, $_, 2);
$value=~ s/%(..)/pack('H2', $1)/ge;
if ($name=~ /list/i){
$req{$name}.= $req{$name} eq '' ? $value : "\0" . $value;
} else {
$req{$name}= $value;
}
}
}
ソースはすべて禁断のSJISで書いてしまいました。。。
まぁ一応はMVCなので文字化けしたところで修正は簡単なので動きを見るだけということで・・
環境変数REQUEST_METHODで場合分けせずに(POSTでURLパラメータ付加値もとれるように)、マルチパートにも対応、クッキー値もセットされるようにしてフォーム部品名にLISTが含まれていれば複数値をヌル文字つながりで取れるようにしてみました。(意外と実践的なデコードだと思います)
■「M」(M.pm)
#
# filename: M.pm
#
# memo: Model(演算)機能を受け持つ
#
use strict;
use constant FORM_START=> <<EOT;
<form action="$ENV{SCRIPT_NAME}" method="post" enctype="multipart/form-data">
<input type="hidden" name="act" value="%act%">
EOT
# コンストラクタ
sub new
{
my $self= bless {
errmsg => "",
}, shift;
return $self;
}
sub seterrmsg
{
my $self= shift;
$self->{errmsg} or $self->{errmsg}= $_[0];
return;
}
sub body_toppage
{
my $self= shift;
return FORM_START . <<EOT;
<input type="submit" value="次へ">
</form>
EOT
}
sub body_next1
{
my $self= shift;
return FORM_START . <<EOT;
<input type="submit" value="次へ">
</form>
EOT
}
sub body_next2
{
my $self= shift;
return FORM_START . <<EOT;
<input type="submit" value="次へ">
</form>
EOT
}
sub body_next3
{
my $self= shift;
return FORM_START . <<EOT;
<input type="submit" value="次へ">
</form>
EOT
}
1;
「M」は前回同様、別モジュールとしてファイル分けしてクラス化しています。(値保持の必要があるので)
■テンプレート(common.tpl)
<head><title>%SITENAME%</title></head>
<body>
<h1>%TITLE%</h1>
<span style="color:red">%errmsg%</span>
<span style="color:black">%message%</span>
<br>
%body%
</body>
</html>
今回は簡易なテスト表示ということで手抜きのテンプレート1つしか用意しませんでした(common.tpl)が、実際にはデザイナーが作ったページに「$keywords%」を埋め込んでいきテンプレート化する流れになると思います。
その際には小分けされたテンプレートに置換用の「%act%」「%SITENAME%」「%TITLE%」などは確定要素なので書かずに値が直書きされる場合が多いと思います。またそのほうがデザインだけを見てどれ用のテンプレートか分かり易いと思います。
そうしない場合は、必要最小限のテンプレートデザインでとことんコンテンツ要素を置換させる、よりシンプルなテンプレートにするという方法もアリだと思います。
[表示結果] act=next1でもnext2でもnext3でもない場合
[表示結果] act=next1
[表示結果] act=next2
[表示結果] act=next3



