网络数据库教程-第5日
| 一 必不可少的CGI.pm 二 数据库转义序列 三 在Here-Document字符串内嵌入引用表达式 四 嵌入子程序 五 用SQL设置表单- selection.iphtml 六 用CGI.pm - receive.iphtml处理表单结果 |
在过去的几天里我们已经谈了许多重要的事项。当然谈得都不是太深入。今天我将用一个实例比较细致地阐述一下过去几天所学的概念。而且几天还会涉及一些新的问题。
在各种有关Perl CGI编程的讨论中常常会提到Lincoln Stein的CGI.pm 。总体来说,CGI.pm 所用的编程理念和我在本教程中所倡导的完全相反。
从一开始,我就在试图帮助你们建立互动、动态生成的网页。这也是嵌入式HTML编程的初衷,而且它也能使网站的编码和组织更简便快捷。
而CGI.pm的观点是将网络编程分离出来,它给你一种感觉仿佛你是在用CGI.pm搭建一个网页,例:
#!/usr/bin/perl
use CGI;
$query = new CGI;
print $query->header(),
$query->start_html(-title=>'Made with CGI.pm'),
'This is what I mean by ',
$query->b('abstracted'),
'.',
$query->end_html();
exit 0;
该程序的执行结果是一个简单的HTTP头,一个起始HTML块(包括一个<TITLE>),几行文字,以及标准的HTML结束块。注意所有这些都是用CGI的$query 对象以及与其相关的方法编制的:
Content-type: text/html <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <HTML><HEAD><TITLE>Made with CGI.pm</TITLE> </HEAD><BODY>This is what I mean by <B>abstracted</B>.</BODY></HTML>
我总是尽量避免使用这种编程方法,但是我也不得不承认这种方法有时候 用起来很顺手。
CGI.pm的优点不在于它的抽象性,而在于它所提供的工具。CGI.pm有一个非常强大的cookie处理器以及表单参数解码方法。
我们都了解表单,它是HTML,而且它具有很多用途。
作为一个网络程序员,表单是从用户处收集的只要方法。当一个用户点击递交(Submit)按钮时,浏览器将用户输入的信息打包编码然后发送给你的CGI程序进行处理。而CGI程序的职责就是要解包,解码和使用表单中的内容。
这个过程很烦琐,为什么不让CGI.pm帮你处理这些事情呢?表单参数可以用param方法提取:
#!/usr/bin/perl
#
# BAR.cgi is a simple Perl CGI program using CGI.pm.
#
# A Web page with a form containing the FOO textarea
# is meant to submit to BAR.cgi (pay attention to
# the "param" method).
#
use CGI;
$query = new CGI;
print $query->header(),
$query->start_html(-title=>'Test of the param method'),
'The value of the FOO parameter is: ',
$query->param('FOO'),
$query->end_html();
exit 0;
这就是用CGI.pm解决这个问题的方法。在以后的某些ePerl代码中我还将用到CGI.pm,但只是为了利用其param方法的优势。
在第3天的教程中我用做了一个唱片数据库,其中有一张唱片叫做"We Can't Dance",这一项不能作为字符串利用mysql程序插入到数据库中-因为MySQL将符号'作为字符串的限位符。
变通方法是通过加入反斜杠将该限位符“转义”:
mysql> insert into albums(title,artist,released)
-> values('We Can\'t Dance','Genesis','1991-01-01');
当然在为了数据库上下文中处理这些细节比较令人头疼。
当你想在数据库中插入项目时,通常会直接使用表单参数。你的ePerl代码可能如下:
$foo = $query->param('foo');
$foo =~ s/'/\\'/g; # this Perl command will substitute ' with \'
$SQL = <<"EOT";
insert into my_table(my_column)
values ('$foo')
EOT
这种设置方法不好,因为:
MySQL用反斜杠"转义"字符,但其他的某些数据库程序则可能使用不同的转义序列,所以用反斜杠硬性“转义”会影响程序的可移植性另外,该转义符在此处只转义了单引号 ' -还有其他的字符也需要转义。
需要转义的其他字符在不同的数据库中会有所区别。
你不需要记忆文献中所述的细节。用$dbh->quote记忆可以帮你实现上述目的。
$foo = $dbh->quote($query->param('foo'));
$query->param('foo')将返回表单文字区输入的foo的值,并且$dbh->quote将根据和你的数据库相应的DBD转义必要的字符。
$dbh->quote还在这个字符串外添加引号,这样就省得你在键盘输入引号了。
here-document字符串的语法如下:
$string = <<"HERE_DOCUMENT";
You can type all sorts of stuff in here....
You can also $interpolate variables right into your h-d string.
The here-document string will quit when it runs into the label
given at its outset.
HERE_DOCUMENT
here_document非常方便,特别是在编制传递给$dbh以生成客户端$cursor光标查询的 $SQL字符串时。
看一下我们如何在上面的$dbh->quote例子生成$SQL。 我必须生成一个中介变量$foo插入实际生成$SQL的here-document字符串。但这样也不是很理想。我们可以做得更好。
问题是,当$dbh->quote返回一个字符串标量值时,它不是字符串标量值,它是一个函数(实际上是一个方法),这个问题可以在here-documents中变通解决。例如,下面的代码就不能执行:
$SQL = <<"EOT";
insert into my_table(my_column)
values ($dbh->quote($query->param('foo')))
EOT
我在here-document做一些变通 (变通方法用加重体字显示)
$SQL = <<"EOT";
insert into my_table(my_column)
values (${ \($dbh->quote($query->param('foo'))) })
EOT
\ 对$dbh->quote所提供的值作了一个引用。${ ... }取消该引用。语法看起来不好看,但是,据我所知,从来也没有人认为Perl 是一种漂亮的语言。
尽管ePerl可以让你在HTML中任意嵌入Perl,但有时候在你的.iphtml文件中
使用子程序能使你的程序显得简洁明了。
例:
<HTML>
<HEAD><TITLE>Embedding a Subroutine</TITLE></HEAD>
<BODY>
<P>
Here's an HTML calendar for the current month:
<B><?=${ \(`cal| head -1`) }!></B>
<P>
<?=${ \( calendar_table() ) }!>//
</BODY>
</HTML>
<?
sub calendar_table {
#
# I make copious use of the Unix "cal" command here.
# This won't work on DOS-derivative machines.
#
my @cal = `cal`; # fill the cal array with the
output of a shelled cal command
my $return = '';
shift @cal; # junk the first line... it's not needed
$return .= "<TABLE BORDER>\n";
$return .= "<TR>";
# I dedicate the following line to my fellow Perl junkies
everywhere
$return .= join('', map { s/\W//g; "<TH>$_</TH>" }
unpack("A2A3A3A3A3A3A3", shift @cal));
$return .= "</TR>\n";
foreach ( @cal ) {
$return .= "<TR>\n";
$return .= join('', map { s/\W//g; "<TD>$_</TD>" }
unpack("A2A3A3A3A3A3A3",$_));
$return .= "</TR>\n";
}
$return .= "</TABLE>\n"; # the value of this line is
returned
}
!>//
这里是该程序执行的结果(如下)。 注意如果子程序非常大的话可能会给内存造成麻烦(因为$return的体积会很大),但是当我们遇到这个问题时我们会用相应的办法解决。
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | ||||
五、 用SQL设置表单 - selection.iphtml
这里是我所用到的例子,它包括两个程序selection.iphtml和receive.iphtml。
首先调用selection.iphtml -
它将提供一个表单。用户填完表单后,表单将被
递交给receive.iphtml。当它列表显示结果后,你将回到selection中。
但是我不能在这里演示插入、更新或删除SQL语句,因为我不能公开该数据库的
内容。
<?
my $dbh = DBI->connect('DBI:mysql:test:localhost', '','',
{ PrintError => 0}) || die $DBI::
errstr;
!>//
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<TITLE>An ePerl Database Example: Fiddling with
Albums</TITLE>
</HEAD>
<BODY>
<P>
Use the following form to query the contents of an
(admittedly limited)
database of CD albums of mine.
<TABLE>
<TR>
<TD><?=${ \( search_by_band ( \$dbh ) ) }!></TD>
<TD><?=${ \( search_by_year ( \$dbh ) ) }!></TD>
</TR>
</TABLE>
<FORM ACTION=receive.iphtml METHOD=POST>
<B>Or, type in a title:</B> <INPUT NAME=title SIZE=20>
(for all titles, just put the cursor in this field
and hit ENTER)
</FORM>
</BODY>
</HTML>
<?
$dbh->disconnect;
!>//
<?
sub search_by_band {
#
# Note that I passed a reference to the database handle dbh.
# This means that, in order to reference it within this
# subroutine, I'll have to refer to it as $$dbh.
#
my $dbh = shift;
my $return = '';
#
# The "distinct" keyword in SQL will only return one row for
a set of
# identical matches. "Order by" will sort the
# returned set alphabetically.
#
my $SQL = <<"EOT";
select distinct artist
from albums
order by artist
EOT
my $cursor = $$dbh->prepare($SQL);
$cursor->execute;
$return .= "<FORM ACTION=receive.iphtml METHOD=POST>\n";
$return .= "<B>Pick an artist:</B><BR>\n";
$return .= "<SELECT NAME=artist>\n";
my @fields;
while ( @fields = $cursor->fetchrow ) {
$return .= "<OPTION>$fields[0]\n";
}
$return .= "</SELECT><BR>\n";
$return .= "<INPUT TYPE=SUBMIT NAME=artist_submit
VALUE=\"Go Search on This Artist!\">\n";
$return .= "</FORM>\n";
}
sub search_by_year {
my $dbh = shift;
my $return = '';
#
# If COLUMN is defined as a date datum, then year(COLUMN)
will return only
# the year portion of the data in the column. "Order by
COLUMN desc"
# will reverse the usual sort order.
#
my $SQL = <<"EOT";
select distinct year(released)
from albums
order by released desc
EOT
my $cursor = $$dbh->prepare($SQL);
$cursor->execute;
$return .= "<FORM ACTION=receive.iphtml METHOD=POST>\n";
$return .= "<B>Or, pick a year:</B><BR>\n";
$return .= "<SELECT NAME=year>\n";
my @fields;
while ( @fields = $cursor->fetchrow ) {
$return .= "<OPTION>$fields[0]\n";
}
$cursor->finish;
$return .= "</SELECT><BR>\n";
$return .= "<INPUT TYPE=SUBMIT NAME=year_submit
VALUE=\"Go Search on This Year!\">\n";
$return .= "</FORM>\n";
}
!>//
六、 用CGI.pm - receive.iphtml处理表单结果
<?
my $cgi = new CGI; # 利用 "param" 解码方法的优势
my $dbh = DBI->connect('DBI:mysql:test:localhost', '','',
{ PrintError => 0}) || die $DBI::errstr;
!>//
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<TITLE>Results from the Database Search</TITLE>
</HEAD>
<BODY>
<P>
<?=${\( display_query_results(\$dbh, \$cgi) )}!>//
<P>
<A HREF=selection.iphtml>Return to the query page</A>
</BODY>
</HTML>
<?
$dbh->disconnect;
!>//
<?
sub display_query_results {
#
# 注意我将引用传递给数据库处理器dbh和
# cgi对象 - 这意味着要想在子程序中引用他们
# 我必须使用$$dbh和$$cgi.
#
my $dbh = shift;
my $cgi = shift;
my $return = '';
my $SQL;
if ( defined($$cgi->param('title')) ) {
my $SQL = <<"EOT";
select title, artist, year(released)
from albums
where ucase(title) like ${ \($$dbh->quote(uc($$cgi->
param('title')) . '%') ) }
order by title, artist
EOT
#
# 在上述语句中我使用了更复杂的SQL。SQL不会
# 自动将返回的结果排序,但是利用
# "order by" 语句可以很容易地实现排序 - 只需设定你希望显示的列数
# 以及它们,的顺序优先级,"Like"可以匹配子程序 -
# 如果你提供了标题 "abc," 则所有标题以"abc"开头的唱片
# 都会被返回。为了消除这种现象,我
# 在SQL中使用ucase(title),以及uc($$cgi->param('title'),
将两个字符串都大写显示。
# %字符是一个通配符,很象UNIX中的*
# 文件名globbing.
#
my $cursor = $$dbh->prepare($SQL);
$cursor->execute;
$return .= "<TABLE BORDER>\n<TR><TH COLSPAN=3>";
$return .= "<B>Matches on the title search for:
<TT><I>${ \($$cgi->param('title') )}</I></TT></B></TH></TR>";
$return .= "<TR><TH>Title</TH><TH>Artist</TH><TH>
Year of Release</TH></TR>\n";
my @fields;
while ( @fields = $cursor->fetchrow ) {
$return .= "<TR><TD>$fields[0]</TD><TD>$fields[1]
</TD><TD>$fields[2]</TD></TR>\n";
}
$cursor->finish;
$return .= "</TABLE>\n";
} else {
if ( defined($$cgi->param('artist_submit')) ) {
$SQL = <<"EOT";
select title, year(released)
from albums
where artist = ${ \($$dbh->quote($$cgi->param('artist'))) }
order by released desc, title
EOT
} elsif ( defined($$cgi->param('year_submit')) ) {
$SQL = <<"EOT";
select artist, title
from albums
where year(released) = ${ \($$dbh->quote($$cgi->param('year'))) }
order by artist, title
EOT
}
my $cursor = $$dbh->prepare($SQL);
$cursor->execute;
$return .= "<TABLE BORDER>\n<TR><TH COLSPAN=2>";
$return .= (defined($$cgi->param('artist_submit'))?
("<B>Artist: <TT><I>".
"${ \($$cgi->param('artist')) }</I>".
"</TT></B></TH></TR>\n<TR>".
"<TH>Album Title</TH>".
"<TH>Year of Release</TH></TR>\n"):
("<B>Year of Release: <TT><I>".
"${ \($$cgi->param('year')) }</I>".
"</TT></B></TH></TR>\n<TR>".
"<TH>Artist</TH><TH>Album Title</TH>".
"</TR>\n"));
my @fields;
while ( @fields = $cursor->fetchrow ) {
$return .= "<TR><TD>$fields[0]</TD>";
$return .= "<TD>$fields[1]</TD></TR>\n";
}
$cursor->finish;
$return .= "</TABLE>\n";
}
$return;
}
!>//
本教程到此就结束了,但是它只是为了数据库编程的入门,而不是结束。我希望该教程能对你有所帮助。