PHP学习(3)——数据的存储与检索
要点目录:
1.保存数据的方法
存储数据有两种基本方法:保存到普通文件,或者保存到数据库中。当具有大量订单时,应该使用一个数据库管理系统。
2.订单添加收货地址
本篇将使用上一篇所介绍的订单的改进版本。
Html代码如下:
<html> <head> <title>Bob's Auto Parts</title> </head> <body> <h1>Bob's Auto Parts</h1> <h2>Order Form</h2> <form action="processorder.php" method="post"> <table border="0"> <tr bgcolor="#cccccc"> <td width="150">Item</td> <td width="15">Quantity</td> </tr> <tr> <td>Tires</td> <td align="left"><input type="text" name="tireqty" size="3" maxlength="3"/></td> </tr> <tr> <td>Oil</td> <td align="left"><input type="text" name="oilqty" size="3" maxlength="3"/></td> </tr> <tr> <td>Spark Plugs</td> <td align="left"><input type="text" name="sparkqty" size="3" maxlength="3"/></td> </tr> <tr> <td>Shipping Address</td> <td align="center"><input type="text" name="address" size="40" maxlength="40"/></td> </tr> <tr> <td colspan="2" align="center"><input type="submit" value="Submit Order"/></td> </tr> </table> </form> </body> </html>
3.打开文件
fopen()函数可以打开一个文件。
$fp = fopen(“$DOCUMENT_ROOT/orders.txt”,’w’);
由于我们为表单变量定义了一个简短名称,我们需要在脚本的开始处加上如下代码:
$DOCUMENT_ROOT = $_SERVER[‘DOCUMENT_ROOT’];
可以通过如下3种方式得到文档根目录:
$_SERVER[‘DOCUMENT_ROOT’] $DOCUMENT_ROOT $HTTP_SERVER_VARS[‘DOCUMENT_ROOT’]
fopen()有四个参数,其中第一个参数是路径。
第二个参数是文件模式。
第三个参数可选,如果要在include_path中搜索一个文件,就可以使用它。
第四个参数也可选,fopen()函数允许文件名称以协议名称开始并且在一个远程的位置打开文件。
也可以使用fopen()函数通过FTP、HTTP或其他协议来打开文件。
注意,URL中的域名不区分大小写,但是路径和文件名可能会区分大小写。
4.打开文件时可能遇到的问题
设置了不正确的访问权限可能是造成打开文件时出现错误的常见原因。
任何人都可以写的目录和文件时非常危险的,后面的文章会详细介绍安全问题。
If语句可以用来测试变量$fp,查看fopen()函数是否返回了一个有效的文件指针。
@$fp = fopen("$DOCUMENT_ROOT/orders.txt", 'ab'); if (!$fp) { echo "<p><strong> Your order could not be processed at this time. Please try again later.</strong></p></body></html>"; exit; }
用自己的错误信息替代PHP的错误信息可以使用户觉得更加友好。
5.写文件
可以使用fwrite()写文件。
int fwrite(resource handle, string string [, int length])
第一个参数是文件。第二个参数是数据。第三个参数可选,是最大字符数。可以通过PHP的内置strlen()函数获得字符串的长度:
fwrite($fp, $outputstring, strlen($outputstring));
定义保存的数据格式:
$outputstring = $date."\t".$tireqty." tires \t".$oilqty." oil\t" .$sparkqty." spark plugs\t\$".$totalamount ."\t". $address."\n";
选择每行记录一个订单这种格式是因为这样可以使用换行字符作为简单的记录间隔符。
处理了一些订单后,该文件的内容将类似如下:
08:00, 9th August 2016 1 tires 1 oil 1 spark plugs $114.00 s
08:51, 9th August 2016 1 tires 1 oil 1 spark plugs $114.00 s
08:51, 9th August 2016 112 tires 121 oil 111 spark plugs $12 854.00 sasdf
6.关闭文件
按照如下方式调用fclose()函数即可:
fclose($fp)
订单处理脚本processorder.php源码:
<?php // create short variable names $tireqty = $_POST['tireqty']; $oilqty = $_POST['oilqty']; $sparkqty = $_POST['sparkqty']; $address = $_POST['address']; $DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT']; $date = date('H:i, jS F Y'); ?> <html> <head> <title>Bob's Auto Parts - Order Results</title> </head> <body> <h1>Bob's Auto Parts</h1> <h2>Order Results</h2> <?php echo "<p>Order processed at ".date('H:i, jS F Y')."</p>"; echo "<p>Your order is as follows: </p>"; $totalqty = 0; $totalqty = $tireqty + $oilqty + $sparkqty; echo "Items ordered: ".$totalqty."<br />"; if ($totalqty == 0) { echo "You did not order anything on the previous page!<br />"; } else { if ($tireqty > 0) { echo $tireqty." tires<br />"; } if ($oilqty > 0) { echo $oilqty." bottles of oil<br />"; } if ($sparkqty > 0) { echo $sparkqty." spark plugs<br />"; } } $totalamount = 0.00; define('TIREPRICE', 100); define('OILPRICE', 10); define('SPARKPRICE', 4); $totalamount = $tireqty * TIREPRICE + $oilqty * OILPRICE + $sparkqty * SPARKPRICE; $totalamount=number_format($totalamount, 2, '.', ' ');//通过千位分组来格式化数字 echo "<p>Total of order is $".$totalamount."</p>"; echo "<p>Address to ship to is ".$address."</p>"; $outputstring = $date."\t".$tireqty." tires \t".$oilqty." oil\t" .$sparkqty." spark plugs\t\$".$totalamount ."\t". $address."\n"; // open file for appending @ $fp = fopen("$DOCUMENT_ROOT/orders.txt", 'ab'); flock($fp, LOCK_EX); //写操作锁定 if (!$fp) { echo "<p><strong> Your order could not be processed at this time. Please try again later.</strong></p></body></html>"; exit; } fwrite($fp, $outputstring, strlen($outputstring)); flock($fp, LOCK_UN); //释放锁定 fclose($fp); echo "<p>Order written.</p>"; ?> </body> </html>
7.读文件
用来查看订单文件的员工界面:
<?php //create short variable name $DOCUMENT_ROOT = $_SERVER['DOCUMENT_ROOT']; ?> <html> <head> <title>Bob's Auto Parts - Customer Orders</title> </head> <body> <h1>Bob's Auto Parts</h1> <h2>Customer Orders</h2> <?php @$fp = fopen("$DOCUMENT_ROOT/orders.txt", 'rb'); //只读模式 flock($fp, LOCK_SH); // lock file for reading if (!$fp) { echo "<p><strong>No orders pending. Please try again later.</strong></p>"; exit; } while (!feof($fp)) { $order= fgets($fp, 999); echo $order."<br />"; } echo "Final position of the file pointer is ".(ftell($fp)); //指针当前位置 echo "<br />"; rewind($fp); echo "After rewind, the position is ".(ftell($fp)); //指针复位 echo "<br />"; flock($fp, LOCK_UN); // release read lock fclose($fp); ?> </body> </html>
这段脚本是按照前面所介绍的步骤进行的:打开文件、读文件、关闭文件。下面进行详细说明。
7.1 知道何时读完文件:feof()
使用feof()函数作为文件结束的测试条件:
while(!feof($fp))
函数feof()的唯一参数是文件指针。如果该文件指针指向了文件末尾,它将返回true。feof表示File End Of File。
7.2 每次读取一行数据:fgets()、fgetss()和fgetcsv()
这个例子中使用了fgets()函数来读取文件内容:
$order = fgets($fp, 999);
这个函数可以从文件中每次读取一行内容。这样,它将不断地读入数据,直至读到一个换行字符(\n)、或者文件结束符EOF,或者是从文件中读取了998B。可以读取的最大长度为指定的长度减去1B。
出于操作安全的考虑可以使用fgetss()函数:
string fgetss(resource fp, int length, string [allowable_tags]);
如果希望重新构建订单中的变量,而不是将整个订单作为一行文本,使用fgetcsv()函数可以很容易实现。
array fgetcsv( resource fp, int length [, string delimiter[, string enclosure]])
7.3 读取整个文件:readfile()、fpassthru()和file()
有四种方式可以一次读取整个文件:
1)readfile()
readfile(“$DOCUMENT_ROOT/orders.txt”);
调用readfile()函数将打开这个文件,并且将文件内容输出到标准输出中,然后再关闭这个文件。readfile()的函数原型如下:
int readfile(string filename, [int use_include_path [, resource context]]);
第二个可选参数指定了PHP是否应该在include_path中查找文件。
2)fpassthru()
要使用这个函数,必须先使用fopen()打开文件。然后将文件指针作为参数传递给fpassthru()。
可以使用如下代码替代前面的脚本:
$fp = fopen(“$DOCUMENT_ROOT/orders.txt”,’rb’); fpassthru($fp);
3)file()
除了可以将文件内容会显到标准输出外,它和readfile()是一样的。
4)file_get_contents()
与readfile()基本相同。不同之处是该函数将以字符串的形式返回文件内容,而不是将文件内容回显到浏览器中。
7.4 读取一个字符:fgetc()
while(!feof($fp)){ $char = fgetc($fp); if(!feof($fp)){ echo ($char == “\n”?”<br />”:$char); } }
这段代码使用fgetc()函数从文件中一次读取一个字符,并且将该字符保存在$char中,直到文件结束。然后再用HTML的换行符(<br />)替换文本中的行结束符(\n)。
使用fgetc()函数的一个缺点就是它返回文件结束符EOF,而fgets()则不会。读取出字符后还需要判断feof(),因为我们并不希望将文件结束符EOF回显到浏览器中。
7.5 读取任意长度:fread()
String fread(resource fp, int length);
8.使用其他有用的文件函数
8.1 查看文件是否存在:file_exists()
if(file_exists(“$DOCUMENT_ROOT/orders.txt”)){ echo ‘There are orders waiting to be processed.’; } else{ echo ‘There are currently no orders.’; }
8.2 确定文件大小:filesize()
echo filesize(“$DOCUMENT_ROOT/orders.txt”);
8.3 删除一个文件:unlink()
unlink(“$DOCUMENT_ROOT/orders.txt”);
通常,如果对该文件的访问权限不够或者该文件不存在,该函数将返回false。
8.4 在文件中定位:rewind()、fseek()和ftell()
rewind()函数可以将文件指针复位到文件的开始。
ftell()函数可以以字节为单位报告文件指针当前在文件中的位置。
fseek()函数将文件指针指向文件的某个位置:
int fseek( resource fp, int offset[, int whence])
9.文件锁定
假设两个客户试图同时订购同一件商品,可以使用文件锁定的方法。当一个文件被打开并且在进行读写操作之前,应该调用这个函数。
bool flock(resource fp, int operation [, int & wouldblock])
还必须将一个指向被打开文件的指针和一个表示所需锁定类型的常数作为参数传递给这个函数。如果文件锁定成功,其返回值为true,否则为false。如果获得文件锁将导致当前的进程被阻塞,可选的第三个参数将包含值true。
operation参数的可能值:
LOCK_SH:读操作锁定
LOCK_EX:写操作锁定
LOCK_UN:释放已有的锁定
LOCK_NB:防止在请求加锁时发生阻塞
flock()函数无法在NFS或其他网络文件系统中使用。在某些操作系统中,它是在进程级别上实现的,如果你在多线程服务器API中使用,该函数也无法正确使用。
如果有两个脚本同时申请对一个文件加锁,就需要使用数据库管理系统(DBMS)。
10.更好的方式:数据库管理系统
相比使用普通文件的优势:
1)更快的数据访问
2)易查找并检索满足特定条件的数据集合
3)具有内置的处理并发访问的机制
4)可以随机访问数据
5)具有内置的权限系统
后面的文章会详细介绍数据库。
整理自《PHP与MySQL Web开发》