在第2章中,我们介绍了如何以普通文件的格式保存Bob汽车零部件商店的订单数据。我们知道,文件I/O(事实上,任何类型的I/O)是程序经常出现错误的地方。这就使得它成为应用异常处理的合理位置。
回顾初始代码,可以看到,写文件时可能会出现三种情况的错误:文件无法打开、无法获得写锁或者文件无法写入。我们为每一种可能性都创建了一个异常类。这些异常类的代码如程序清单7-4所示。
程序清单7-4 file_exceptions.php——文件I/O相关的异常
<?php
class fileOpenException extends Exception
{
function__toString
{
return\"fileOpenException\".$this->getCode
.\":\".$this->getMessage.\"<br/>\".\"in\"
.$this->getFile.\"on line\".$this->getLine
.\"<br/>\";
}
}
class fileWriteException extends Exception
{
function__toString
{
return\"fileWriteException\".$this->getCode
.\":\".$this->getMessage.\"<br/>\".\"in\"
.$this->getFile.\"on line\".$this->getLine
.\"<br/>\";
}
}
class fileLockException extends Exception
{
function__toString
{
return\"fileLockException\".$this->getCode
.\":\".$this->getMessage.\"<br/>\".\"in\"
.$this->getFile.\"on line\".$this->getLine
.\"<br/>\";
}
}
?>
Exception类的这些子类并没有执行任何特别的操作。事实上,对于这个应用程序的作用来说,可以让它们成为空的子类或者使用PHP所提供的Exception类。然而,我们为每一个子类提供了_toString方法,从而可以解释所发生的异常类型。
我们重新编写了第2章的processorder.php文件,在其中使用了异常。该文件的新版本如程序清单7-5所示。
程序清单7-5 processorder.php——Bob汽车零部件商店程序的订单处理脚本,该脚本已经包括了异常处理
<?php
require_once(\"file_exceptions.php\");
//create short variable names
$tireqty=$_POST[\'tireqty\'];
$oilqty=$_POST[\'oilqty\'];
$sparkqty=$_POST[\'sparkqty\'];
$address=$_POST[\'address\'];
$DOCUMENT_ROOT=$_SERVER[\'DOCUMENT_ROOT\'];
?>
<html>
<head>
<title>Bob\'s Auto Parts-Order Results</title>
</head>
<body>
<h1>Bob\'s Auto Parts</h1>
<h2>Order Results</h2>
<?php
$date=date(\'H:i,jS F\');
echo\"<p>Order processed at\".$date.\"</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.\"tirest\".$oilqty.\"oilt\"
.$sparkqty.\"spark plugst$\".$totalamount
.\"t\".$address.\"n\";
//open file for appending
try
{
if(!($fp=@fopen(\"$DOCUMENT_ROOT/../orders/orders.txt\",\'ab\')))
throw new fileOpenException;
if(!flock($fp,LOCK_EX))
throw new fileLockException;
if(!fwrite($fp,$outputstring,strlen($outputstring)))
throw new fileWriteException;
flock($fp,LOCK_UN);
fclose($fp);
echo\"<p>Order written.</p>\";
}
catch(fileOpenException$foe)
{
echo\"<p><strong>Orders file could not be opened.
Please contact our webmaster for help.</strong></p>\";
}
catch(Exception$e)
{
echo\"<p><strong>Your order could not be processed at this time.
Please try again later.</strong></p>\";
}
?>
</body>
</html>
可以看到,以上脚本的文件I/O部分被封装在一个try代码块中。通常,良好的编码习惯要求try代码块的代码量较少,并且在代码块的结束处捕获相关异常。这使得异常处理代码更容易编写和维护,因为可以看到所处理的内容。
如果无法打开文件,将抛出一个fileOpenException异常;如果无法锁定该文件,将抛出一个fileLockException异常;而如果无法写这个文件,将抛出一个fileWriteException异常。
分析catch代码块。要说明这一点,我们只给出了两个catch代码块:一个用来处理fileOpen-Exception异常,而另一个用来处理Exception。由于其他异常都是从Exception继承过来的,它们将被第二个catch代码块捕获。catch代码块与每一个instanceof操作符相匹配。这就是为每一个类扩展自己异常类的理由。
一个重要警告:如果异常没有匹配的catch语句块,PHP将报告一个致命错误。