7.4 Bob的汽车零部件商店应用程序的异常
在第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."tires\t".$oilqty."oil\t"
.$sparkqty."spark plugs\t\$".$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将报告一个致命错误。