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将报告一个致命错误。