本文最后更新于:2024年3月18日 凌晨
PHP 表单处理
因为表单变量可以通过$_GET
和$_POST
数组得到,所以用PHP处理表单是十分容易的,尽管如此,表单处理还是有很多技巧的,本节将进行介绍。
方法(Method)
如前所述,客户端可以用两种HTTP方法向服务器传送表单数据:GET和POST,采用哪种方法是由某个表单标签(
一个GET请求把表单的参数编码成URL形式,这称也查询字符串(query string):
1 /path/to/chunkify.php?word+despocable&length=3
一个POST请求则通过HTTP请求的主体来传递表单参数,不需要考虑URL
GET和POST方法的最明显区别在于URL行,因为GET请求的所有表单参数都编码在URL中,用户可以把一个GET请求加入浏览器收藏夹,而对POST请求却无法这样做。
GET和POST请求之间的最大不同是相当微妙的,HTTP规范指明GET请求是幂等的(idempotent),也就是说,一个对于一个特定URL的GET请求(包含表单参数),与对应于这一特定URL的两个或多个GET请求是一样的,因此,Web浏览器可以把GET请求得到的响应页面缓存起来,这是因为不管页面被请求了多少次,响应页面都是不变的,正因为幂等性,GET请求只适用于那些响应页面永不改变的情况,例如将一个单词分解成小块,或者对数字进行乘法运算。
POST请求不具幂等性,这意味着它们无法被缓存,在每次刷新页面时,都会重新连接服务器,显示或者刷新页面时,你可能会看到浏览器提示:“Repost form data?(重新发送表单数据)”,所以POST适用于响应内容可能会随时间改变的情况,例如:显示购物车的内容,或者在一个论坛中显示当前主题。
现实中,幂等性常常被忽略,目前浏览器的缓存功能都很差,并且"刷新"按钮很容易用户点到,所以程序员通常只考虑是否想将参数显示在浏览器的URL地址栏上,如果不想显示,就用POST方法,但你要记住,在服务器的响应页面可能会变化的情况下(例如下订单或者更新数据库),不要使用GET方法。
一个请求所用的方法类型可以用$_SERVER['REQUEST_METHOD']
来获知,如:
1 2 3 4 5 6 if ($_SERVER ['REQUEST_METHOD' ] == 'GET' ){ }else { die ("You may only GET this page." ); }
参数
在PHP程序中我们可使用$_POST
,$_GET
和$_FILES
数组来访问表单的参数,数组的键名时表单参数的名称,数组元素的值是表单参数的值,因为句点"."在HTML的表单字段命名中是合法的,但是在PHP的变量命名中是不合法的,所以在数组中字符名中的句点被转换成了下划线_
下例展示了一个能将用户提供的字符串分块的表单,表单包含两个字段,一个用来放字符串,名为"word",另一个存放块的大小,名为"number"
示例7-1 :字符串分块表单页面。
1 2 3 4 5 6 7 8 9 10 11 <html> <head><title>Chunkify Form</title></head> <body> <form action="chunkify.php" method="POST"> Enter a word:<input type="text" name="word"/><br/> How long should the chunks be? <input type="text" name="number"/><br/> <input type="submit" value="Chunkify!"> </form> </body> </html>
示例7-2列出了chunkify.php的PHP代码,也就是上例7-1中提交所用到的PHP脚本,这段代码把表单参数的值存入变量并使用,在php.ini中有一个选项register_globals,如果设置为On则可以自动为表单参数生成变量,但是我们通常不这么做,因为这样会影响程序的安全性。
示例7-2 :字符串分块程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <html> <head><title>Chunked Word</title></head> <body> <?php $word = $_POST['word']; $number = $_POST['number']; $chunks = ceil(strlen($word)/$number); echo "The $number-letter chunks of '$word' are:<br/>\n"; for($i = 0;$i < $chunks;$++){ $chunk = substr($word,$i*$number,$number); printf("%d: %s<br/>\n",$i+1,$chunks); } ?> </body> </html>
对参数进行自动引号处理
默认情况下php.ini中的magic_quotes_gpc()
选项启用,该选项指示PHP在所有cookie数据以及GET和POST参数上自动调用addslashes()
函数,这使得在数据库查询中使用表单参数变得简单,但同时也对那些没有在数据库查询中使用的表单参数造成了麻烦,因为只需要在单引号,双引号,反斜杠和空字节等前面添加上反斜杠以进行转义(编写PHP程序时依赖magic_quotes_gpc
将导致很多问题,所以在新版的PHP所带的php.ini中magic_qutoes_gpc
默认设置为不启用,而不是本书所说的默认为启用,在PHP6中,本特性将被彻底移出)
例如,如果你在文本框总输入"O’Reilly"并点击按钮,你就会发现被分块的字符串其实是"O'Reilly",这就说明magic_quotes_gpc
在工作。
为了处理用户输入的字符串,可以禁用php.ini中的magic_quotes_gpc
选项或者对$_GET,$_POST和$_COOKIES使用stripslashes()
函数,正确使用字符串的方法如下:
1 $value = ini_get('magic_quotes_gpc' ) ? stripslashes($_GET ['word' ]) : $_GET ['word' ];
如果要处理大量字符串,为此定义一个函数是聪明的做法:
1 2 3 function raw_param ($name ) { return ini_get('magic_quotes_gpc' ) ? stripslashes($_GET ['word' ]) : $_GET ['word' ]; }
1 $value = raw_param('word' );
提示 :在本章后面的例子中,假设你的php.ini中的magic_quotes_gpc
选项是不启用的,如果你已经启用,你需要改写示例中的代码,对所有参数使用stripslashes()
函数。
自处理页面
一个PHP页面能同时用来生成表单和处理表单,如果示例7-3中页面是用GET方法请求的,它将打印一个接收华氏温度的表单,如果是用POST方法调用的,它将计算并显示相应的摄氏温度。
示例7-3 :自处理温度转换页面(temp.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <html> <head><title>Temperature Conversion</title></head> <body> <?php if($_SERVER['REQUEST_METHOD'] == 'GET'){ ?> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST"> Fahrenheit temperature: <input type="text" name="fahrenheit" /><br/> <input type="submit" name="Convert to Celsius!"/> </form> <?php }else if($_SERVER['REQUEST_METHOD'] == 'POST'){ $fahr = $_POST['fahrenheit']; $celsius = ($fahr - 32)*5/9; printf("%.2fF is %.2fC",$fahr,$celsius); }else{ die("This script only works with GET and POST requests."); } ?> </body> </html>
另一个决定显示表单还是处理表单的方法时,是看是否已经提供了其中的一个参数,这使得你能编写一个使用GET方法提交值的自处理页面,示例7-4显示温度转换程序的新版本,这个程序用是否提供了某个参数来决定做什么。
示例7-4 :使用GET方法进行温度转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <html> <head><title>Temperature Conversion</title></head> <body> <?php $fahr = $_GET['fahrenheit']; if(is_null($fahr)){ ?> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method= "GET"> Fahrenheit temperature: <input type="text" name="fahrenheit"/><br/> <input type="submit" name="Convert to Celsius!"/> </form> <?php }else{ $celsius = ($fahr-32)*5/9; printf("%.2fF is %.2fC",$fahr,$celsius); } ?> </body> </html>
在上例中,我们把表单参数复制到了$fahr
中,如果我们没有给出那个参数,$fahr的内容为NULL,所以我们可用is_null()
函数判断是要显示表单还是处理表单数据。
粘性表单
很多网站使用一种称为"粘性表单"(sticky form)的技术,用这种技术,一个查询表单的默认值就是先前查询的值,例如:如果你在Google(http://www.google.com )上查询"Programming PHP",则在结果页面的顶端的另一个查询文本框中,包含了先前的查询关键字:“Programming PHP”,如果要将查询的文字改为"Programming PHP from O’Reilly",你只要简单地在后面补充即可。
这种"粘性"行为很容易实现,示例7-5展示了基于示例7-4的温度转换程序,使用了新的粘性特性,基本的原理是在创建HTML字段时使用提交的表单值作为默认值。
示例7-5 :使用粘性表单的温度转换程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <html> <head><title>Temperature Conversion</title></head> <body> <?php $fahr = $_GET['fahrenheit']; ?> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET"> Fahrenheit temperature: <input type="text" name="fahrenheit" value="<?php echo $fahr ?>" /> <br/> <input type="submit" name="Convert to Celsius!" /> </form> <?php if(!is_null($fahr)){ $celsius = ($fahr - 32)*5/9; printf("%.2fF is %.2fC",$fahr,$celsius); } ?> </body> </html>
多值参数
用HTML中的select标签创建选择列表,允许用户进行多重选择,为了确保PHP识别浏览器传递来的多重值,你需要在HTML表单的字段名后加上"[]",如:
1 2 3 4 5 6 <select name ="languages[]" > <input name ="c" > C</input > <input name ="c++" > C++</input > <input name ="php" > PHP</input > <input name ="perl" > Perl</input > </select >
现在,当用户提交表单时,$_GET[‘languages’]
包含一个数组而不是一个字符串,这个数组包含用户所选择的值。
下例演示了多种选择,表单为用户提供了一系列个性特征,当用户提交表单时,他可以得到有关他的个性的描述信息(不怎么有趣)
示例7-6 :用选择框进行多重选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <html> <head><title>Personality</title></head> <body> <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET"> Select your personality attributes:<br/> <select name="attributes[]" multiple> <option value="perky">Perky</option> <option value="morose">Morose</option> <option value="thinking">Thinking</option> <option value="feeling">Feeling</option> <option value="thrifty">Spend-thrift</option> <opiton value="prodigal">Shopper</option> </select> <br/> <input type="submit" name="s" value="Record my personality!" /> </form> <?php if(array_key_exists('s',$_GET)){ $description = join(" ",$_GET['attributes']); echo "You have a $description personality."; } ?> </body> </html>
在上例中,提交按钮有一个名称"s",我们检查这个参数是否存在,来判断是否需要产生个性信息的描述。
对于可以返回多重值的表单字段可以使用同样的技术,下例是个人信息表单的新版,使用了复选框(check box)而不是选择框(select box),注意只是HTML发生了改变----那些处理表单的PHP代码无须改变,因为它不需要知道多重值是来自于复选框还是选择框。
示例7-7 :使用复选框的多重选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <html> <head><title>Personality</title></head> <body> <form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET"> Select your personality attributes:<br/> Perky<input type="checkbox" name="attributes[]" value="perky" /><br/> Morose<input type="checkbox" name="attributes[]" value="morose" /><br/> Thinking<input type="checkbox" name="attributes[]" value="thinking" /><br/> Feeling<input type="checkbox" name="attributes[]" value="feeling" /><br/> Spend-thrift<input type="checkbox" name="attributes[]" value="thrifty" /><br/> Shopper<input type="checkbox" name="attributes[]" value="prodigal" /><br/> <br/> <input type="submit" name="s" value="Record my Personality!" /> </form> <?php if(arrat_key_exists('s',$_GET)){ $description = join(" ",$GET['attributes']); echo "You have a $description personality."; } ?> </body> </html>
粘性多值参数
现在你可能想知道:是否可以让一个多重选择的表单也具有粘性?答案是可以,但是不太容易,你需要检查表单中的每一个可能值是否是提交值之一,例如:
1 2 3 4 5 Perky: <input type="checkbox" name="attributues[]" value="perky" <?= if(is_array($_GET['attributes']) and in_array('perky',$_GET['attributes'])){ "checked"; } ?> /><br/>
你可以在每一个复选框上使用该技术,但那是重复性的操作,且容易出错,更简单的方法是编写一个函数为可能值生成HTML和对提交值的一个副本进行操作,示例7-8展示了多重选择的新版本,使表单具有粘性,尽管这个表单和示例7-7中的看起来一样,但是生成表单的方法发生了本质上的改变。
示例7-8 :粘性多值复选框。
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 31 32 33 34 35 36 37 38 39 40 41 <html> <head><title>Personality</title></head> <body> <?php // 如果存在,则获取表单值。 $attrs = $_GET['attributes']; if(!is_array($attrs)){ $attrs = array(); } // 创建具有相同名称的HTML复选框。 function make_checkboxes($name,$query,$option){ foreach($options as $value => $label){ printf('%s <input type="checkbox" name="%s[]" value="%s" ',$label,$name,$value); if(in_array($value,$query)){ echo "checked": } echo "/><br/>\n"; } } // 复选框的值和标签列表。 $personality_attributes = array( 'perky' => 'Perky', 'morose'=> 'Morose', 'thinking' => 'Thinking', 'feeling' => 'Feeling', 'thrifty' => 'Spend-thrift', 'prodigal' => 'Shopper' ); ?> <form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET"> Select your personality attributes:<br/> <?php make_checkboxes('attribures',$attrs,$personality_attributes); ?> <br/> <input type="submit" name="s" value="Record my Personality!" /> </form> <?php if(arrat_key_exists('s',$_GET)){ $description = join(" ",$GET['attributes']); echo "You have a $description personality."; } ?> </body> </html>
这段代码的核心是make_checkboxes()
函数,它有3个参数:复选框组的名称,默认值数值和将值映射到描述的数组,复选框的选项列表在$personality_attributes数组中。
文件上传
处理文件上传(现在大部分浏览器都支持该操作)可用$_FILES数组,你可以使用各种验证和文件上传函数,你可以控制谁允许上传,上传的文件如何处理。
下面的代码显示了一个用于上传文件的表单,文件上传由当前页面处理。
1 2 3 4 <form enctype="multipart/form-data" action="<?=$_SERVER['PHP_SELF'] ?>" method="POST"> <input type="hidden" name="MAX_FILE_SIZE" value="10240"> File name:<input name="toProcess" type="Upload"> </form>
上传文件最大的问题在于上传一个太大的无法处理的文件,PHP有两种方法避免出现这种情况,PHP有两种办法避免出现这种情况:一个硬性限制和一个软性限制。
php.ini中的upload_max_filesize
选项可以硬性指定上传文件的大小上限(默认为2M),如果你的表单在任何文字字符参数提交前提交了一个名为MAX_FILE_SEZE
的参数,则PHP将使用那个值作为文件大小的软限制,例如,在上面例子中,文件大小的上限为10KB,PHP将忽略大于upload_max_filesize
的MAX_FILE_SEZE
参数。
$_FILES
数组中的每个元素也都是一个数组,带有上传文件的信息,键名为:
name :浏览器提供的文件名,我们很难使用该值,因为客户端机器上的文件名约定有可能和Web服务器的不同(比如,如果客户机为Windows系统,文件名可能为"D:\PHOTOS|ME.JPG",而服务器为Unix系统,那么这个文件路径没什么意义)
type :上传文件的MIME类型。
size :上传文件的大小(以字节为单位),如果用户试图上传一个过大的文件,它的大小将被置为0
tmp_name :上传文件在服务器中的临时文件名,如果用户试图上传一个过大的文件,它的大小将为置为0
检测一个文件是否已将成功上传的正确方法是使用io_uploaded_file()
函数:
1 2 3 if (is_uploaded_file($_FILES ['toProcess' ]['tmp_name' ]){ }
上传的文件被存储在服务器的默认临时文件夹中,该目录由php.ini中的upload_tmp_dir
选项指定,可使用move_uploaded_file()
函数来移动临时文件。
1 move_uploaded_files($_FILES ['toProcess' ]['tmp_name' ],"path/to/put/file/$file " );
调用move_uploaded_file()
会自动检测文件是否为一个上传的文件,当PHP脚本执行完毕,任何由该脚本上传的文件会从临时文件夹中删除。
表单验证
但你允许输入数据时,你通常需要在使用和存储这些数据之前进行验证,有多种验证用户数据的方法,首先是在客户端使用JavaScript,但是用户可以禁用JavaScript,甚至使用一个不支持JavaScript的浏览器,所以用这个方法还不够。
更安全的选择是使用PHP来完成验证工作,下例展示了一个带有表单的自处理页面,这个页面允许用户输入一个媒体项,表单的三个元素----名称,媒体类型和文件名是必须输入的,如果用户没有输入其中一项,页面将重新显示,并详细地指出错在哪里,表单字段的值将由用户的输入确定,最后,作为对用户的提醒,当用户更正表单时,提交按钮上的文字从"Create"变成了"Continue"
示例7-9 :表单验证。
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 31 32 33 34 35 36 37 38 39 40 41 <?php $name = $_POST['name']; $media_type = $_POST['media_type']; $filename = $_POST['filename']; $caption = $_POST['caption']; $tried = ($POST['tried'] == 'yes'); if($tried){ $validated = (!empty($name)&&!empty($media_type)&&!empty($filename)); if(!$validated){ ?> <p> The name,media type, and filename are required fields. Please fill them out to continue. </p> <?php } } if($tried && $validated){ echo '<p>The item has been created.</p>'; } // 媒体类型是否被选中? 是则输出"selected" function media_selected($type){ global $media_type; if($media_type == $type){echo "selected";} } ?> <form action="<?= $_SERVER['PHP_SELF'] ?>" method="POST"> Name:<input type="checkbox" name="status" value="active" <?php if($status == 'active'){echo 'checked';} ?> />Active<br/> Media:<select name="media_type"> <option value="">Choose one</option> <option value="picture" <?php media_selected('picture') ?> />Picture</option> <option value="audio" <?php media_selected('audio') ?> />Picture</option> <option value="movie" <?php media_selected('movie') ?> />Picture</option> </select><br/> File:<input type="text" name="filename" value="<?= $filename ?>" /><br/> Caption:<texarea name="caption"><? $caption ?></textrea><br/> <input type="hidden" name"tried" value="yes" /> <input type="submit" value="<?php echo $tried ? 'Continue' : 'Create'; ?>" /> </form>
在这个例子中,验证只是简单地检查值是否已经提供,当$name,$type,$filename都不为空时,我们将$validated设置为true,其他可能的验证包括:检查E-mail地址是否合法,确认提供的文件名是否是本地的,确认文件是否存在。
例如,年龄字段的验证是看输入的是否为一个正整数:
1 2 $age = $_POST ['age' ];$valid_age = strspn($age ,"1234567890" ) == strlen($age );
通过调用strspn()
取得字符串中最靠前的数字,一个非负整数仅由数字组成,所以判断年龄是否合法是看它是否全由数字组成,我们也可以用正则表达式进行检查:
1 $valid_age = preg_match('/^\d+$/' ,$age );
验证E-mail地址是一个很困难的工作,没有现存的方法来检查一个字符串是否满足合法E-mail地址的要求,然而你可以要求用户输入E-mail地址两次(在不同的文本框中),也可以用如下的方法防止用户输入"me"或者"me@aol"这样的E-mail地址:要求所有输入的字符串都包含一个@符号,并且其中有一个点号,你也可以检查不想向其发送E-mail的域(如whitehouse.gov或竞争对手),例如:
1 2 3 4 5 6 7 8 9 10 11 $email1 = strtolower($_POST ['email1' ]);$email2 = strtolower($_POST ['email2' ]);if ($email1 !== $email2 ){ die ("The email addresses didn't match" ); }if (!preg_match('/@.+\..+$/' ,$email1 ){ die ("The email address is invalid" ); }if (strpos($email ,"Whitehorse.gov" )){ die ("I will not send mail to the White House" ): }
表单字段验证是基本的字符串操作,在本例中,我们使用了正则表达式和字符串函数来保证用户所输入的字符串符号我们的要求。