分类:
2008-05-13 16:38:09
PEAR提供了强大的错误处理机制。这篇文章向你展示如何从这个系统中获益。
许多程序已经使用了PEAR的包。许多PHP程序员或多或少的熟悉了PEAR中的错误处理。但是这个机制并不局限于PEAR的包——所有人都能在他们的类和程序中使用这些方法。
这篇文章被分为两个部分:首先我们将看看类中用于错误处理的函数,然后我们将看看如何基于PEAR错误处理机制来处理错误。
我们的例子类称为cvs2db,它把数据从CSV文件插入到数据库的表中。因为数据可能是手写的,他们的数据应该在插入之前先得到验证——落实postcode。函数import()完成读入,检查和插入的工作;它返回损坏的记录数目。如果返回的值大于0,出错的记录集能够使用exportUnvalid()写入到新的CSV文件中。典型的用法是这样的:
import("./dat.csv", $dsn, 'address')) { $cd->exportUnvalid("./dat2.csv"); } ?>
import("./dat.csv", $dsn, 'address') switch($result) { case FILE_NOT_OPENED: ... break; case DATABASE_ERROR: ... break; default: if(0 < $result) { $cd->exportUnvalid("./dat2.csv"); } else { echo 'every thing ok!' } } ?>
// per instance $cd = new csv2db(); $cd->setErrorHandling(PEAR_ERROR_DIE): // static CVS2DB::setErrorHandling(PEAR_ERROR_DIE); PEAR::setErrorHandling(PEAR_ERROR_DIE);
function raiseError(...,$mode=null, $options=null,...) { if($mode==null && $this->_default_error_mode!=null) { $mode = $this->_default_error_mode; $options = $this->_default_error_options; } return PEAR::raiseError(...,$mode, $options,...); }
$db->setErrorhandling(PEAR_ERROR_RETURN) if(!csv2db::isError(0 < $d = $cd->import("./dat.csv", $dsn, 'address'))) { if(!csv2db::isError($cd->exportUnvalid("./dat2.csv")) { } else { // handle error } } else { // handle error }
$cd = new csv2db(); $cd->setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); $dsn = 'mysql://root@localhost/csv2db'; if( 0 < $d = $cd->import("./dat.csv", $dsn, 'address')) { $cd->exportUnvalid("./dat2.csv"); } function handleError($error) { if(DB::isError($error) { // handle database error } if(csv2db::isError($error) { switch($error->getCode()) { case FILE_NOT_OPENED : ... break; case CORRUPTED_RECORD : ... break; } } }
$cd->setErrorHandling(PEAR_ERROR_DIE); ... $cd->expectError(CORRUPTED_RECORD); $cd->import(...); $cd->popExpect();
PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $cd->exportUnvalid("./dat2.csv"); PEAR::popErrorHandling();
PEAR("CSV2DB_Error"); } function import($file, $dsn, $table) { $this->PEAR("CSV2DB_Error"); if($fp=@fopen($file, 'r')) { while($data=fgetcsv($fp, 1024,';')) { $this->records[]=$data; } fclose($fp); } else { return $this->raiseError(null, FILE_NOT_OPENED); } $unvalidCount=0; $storeMode = $GLOBALS['_PEAR_default_error_mode']; $storeOpts = $GLOBALS['_PEAR_default_error_options']; $GLOBALS['_PEAR_default_error_mode'] = $this->_default_error_mode; $GLOBALS['_PEAR_default_error_options'] = $this->_default_error_options; $db = DB::connect($dsn); $GLOBALS['_PEAR_default_error_mode']= $storeMode; $GLOBALS['_PEAR_default_error_options'] = $storeOpts; if(!DB::isError($db)) { $db->setErrorHandling($this->_default_error_mode, $this->_default_error_options); $qp = $db->prepare("INSERT INTO $table VALUES (?, ?, ?, ?)"); foreach( $this->records as $record) { if(preg_match('/\d{5}/',$record[2])) { $db->execute($qp, $record); } else { $unvalidCount ; $this->unvalid[]=$record; $this->raiseError(corrupted record, CORRUPTED_RECORD); } } $db->disconnect(); } else { return $db; } return $unvalidCount; } function exportUnvalid($file) { if($fp=@fopen($file, "w")) { foreach($this->unvalid as $data) { fwrite($fp, implode(';', $data)."\n", 1024); } fclose($fp); } else { return $this->raiseError(null,FILE_NOT_OPENED); } } function isError($data) { return (bool)(is_object($data) && (get_class($data) == 'CSV2DB_Error' || is_subclass_of($data, 'CSV2DB_Error'))); } } class CSV2DB_Error extends PEAR_Error { var $msgs = array( FILE_NOT_OPENED => array( 'de' =>"Datei konnte nicht ge?ffnet werden", 'en' => "File couldn't be opened"), CORRUPTED_RECORD => array( 'de' =>"fehlerhafter Datensatz", 'en' => "corrupted record") ); function CSV2DB_Error($message=null, $code = null, $mode = null, $level = null, $debuginfo = null) { $this->PEAR_Error(null, $code, $mode, $level, $debuginfo); } function getMessage($lang = "en") { return $this->msgs[$this->code][$lang]; } } ?>
自己的错误对象
有一个自己的错误类总是好的,虽然它可能对于这么一个小的类来说是太大的额外负担——但是这个类仅仅是一个例子并且你从如果没有错误对象需要很多代码来实现的特性那儿获益良多。好处是:首先错误是直接赋给类的;以及本地化变得更加容易。
类必须从PEAR_Error继承而来,为的是保持我们的实现简单,否则PEAR::isError()将不能正常工作。
实现包含了构造函数,其中没有改变地把参数传递给了PEAR_Error地构造函数。
改写getMessage()函数是提供本地化错误信息地关键。错误定西被定义为类的变量并且将取决于语言动态的赋值。这也将帮助消息聚集于一处——而不是把他们分散于整个主要类的源代码中。
实现PEAR错误处理
你在文章的第一部分看到了我们的类提供了一堆函数——但是他们中的仅仅有四个是直接实现的。所有的相关函数的错误处理是由PEAR基类提供的。为了从所有那些错误处理特性中获益,我们必须让cvs2db类从PEAR基类继承,也就是:class csv2db extends PEAR。
在前面的错误对象段落中,我从对isError()的解释开始。覆盖这个方法不是必要的,虽然它确实使得我们能够直接检查我们的错误类,并且使得错误跟踪更加精确并且可能节约了几毫秒。
类的构造函数仅仅只是用错误类名称最为参数调用了父类的构造函数。这个调用注册了我们的错误对象并且确保了我们的错误类在每次触发错误的时候被使用。
raiseError
在import()和exportUnvalid()的函数体中对raiseError()的使用是值得注意的。这是创建错误的关键函数;PEAR提供两个函数用于这个目的:raiseError() 和 throwError()。后一个自从PHP 4.3开始存活在并且是raiseError()的一个简化变体,两者行为是一致的;它们的参数在段落 'raiseError 和 throwError' 中描述。
raiseError 和 throwError
原型:
&raiseError( $message, $code, $mode, $options, $userinfo, $errorclass, $skipmessage) &throwError( $message, $code, $userinfo) Parameter Description $message (string) The error message $code (int) The error number $mode (constant) Error mode $options (mixed) Error mode specific parameters $userinfo (mixed) additional data (ie. Debug information) $errorclass (string) A class name
可选的你能够把已经存在的错误对象传递给这些函数:
&raiseError($error_object) &throwError($error_object)
如果你从源代码比较这两个函数的参数表你将看到类并没有设置message参数——这是不必须的因为我们在错误类中用 getMessage() 函数赋给错误信息。而且,调用PEAR构造函数来引入你的错误类也是不必要的,你可以在对 raiseError() 调用中指定错误类。在脑子中记住这个选项!例如,如果你的类提供了静态函数或者多于一个错误对象,你不能给你的类像我们在csv2db中做得那样全局地设置它们。
raiseError() 和 throwError() 能够被静态地调用以及像 setErrorHandling() 那样作为实体函数来调用。当你作不作静态调用地时候做正确地决定是重要的——它直接影响了用户如何用setErrorHandling()来错作我们的类。留意 setErrorHandling() 和 raiseError(),这将避免你和你的用户的头疼。
从类的这个部分能够看到全局和局部的错误设置和触发的负面影响。
$storeMode = $GLOBALS['_PEAR_default_error_mode']; $storeOpts = $GLOBALS['_PEAR_default_error_options']; $GLOBALS['_PEAR_default_error_mode'] = $this->_default_error_mode; $GLOBALS['_PEAR_default_error_options'] = $this->_default_error_options; $db = DB::connect($dsn); $GLOBALS['_PEAR_default_error_mode'] = $storeMode; $GLOBALS['_PEAR_default_error_options'] = $storeOpts;
首先,全局的错误模式被保存了,然后全局的错误模式设置给了局部的错误模式并且最后几行,原来的错误模式被还原了。为什么?Connect()是一个静态函数!它必须使用PEAR::raiseError()。因而假如我们不保存并且还原设置,我们会遇到问题:看看listing 3——如果类在import()函数不能连接到数据库的时候会发生什么?因为对raiseError()的静态调用受到全局错误模式的影响,而不是局部的$cd->setErrorHandling(...)的影响,脚本终止执行 。实际上push和popErrorHandling()就是设计来用于这样的任务的——但是PHP中一个现下的bug看上去不幸的组织了它很好的工作。
强制$db对象使用我们的错误模式是更舒服的方式,它支持完整的PEAR Error API,这使得代码能这样写:$db->setErrorHandling($this->_default_error_mode, $this->_default_error_options)。两个实体变量都是由PEAR_Error类提供的。
那行$this->raiseError(corrupted record, CORRUPTED_RECORD)看上去值得注意——而且缺失的返回看上去不顺眼。原因是:我们不想在发现损坏的记录时中止函数执行。你能把这个和触发一个警告进行比照。唯一的限制时模式PEAR_ERROR_RETURN没有工作。
Listing 3
setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); $dsn = 'mysql://root@localhost/csv2db'; if( 0 < $d = $cd->import("./dat.csv", $dsn, 'address')) { $cd->exportUnvalid("./dat2.csv"); } $db = DB::connect($dsn); $db->query(...); ... function handleError($error) { if(DB::isError($error) { // handle database error } if(csv2db::isError($error) { switch($error->getCode()) { case FILE_NOT_OPENED : ... break; case CORRUPTED_RECORD : ... break; } } } ?>
PEAR错误处理和PHP 5
因为我们使用函数来创建错误,我们没有考虑在PHP 5中的try/catch/throw机制;raiseMethod和throwError将为你完成这些!对于PHP 5,函数能够为你的类透明地调用抛出PEAR_Error()——错误模式PEAR_ERROR_EXCEPTION能够用于这个目的。一下的代码应该能够在不改变类的情况下用于PHP5中:
import("./dat.csv", $dsn, 'address')) { $i->exportUnvalid("./dat2.csv"); } } catch CSV2DB_Error { // fetch the error } ?>
结论
我希望你大概了解了PEAR错误处理,它提供了排除和处理错误的强大机制。看看PEAR手册[1]的代码部分并且找出这些函数提供的好处。
Alexander Merz (alexmerz at php dot net) 是PEAR手册的编辑并且以自由创作者和作家为职业。