太原PHP培训
达内太原php培训中心

0351-5608878

热门课程

如何组织PHP中的异常(一)

  • 时间:2016-11-09
  • 发布:PHPer的进击之路
  • 来源:PHPer的进击之路

如何组织PHP中的异常(一)

本文的主题是怎么组织php的异常?在大型项目中异常往往被我们忽略,但是如果前期没有很好的规划好,越到项目后期,重构的成本会越大。

在实际工作中,对于错误的处理,我们一帮都是直接返回错误号,然后从最内层一层一层往外面传,最后将错误返回给用户,很少使用异常,可能是因为公司里最初写代码比较早,1314年开始使用php,当时第一批使用者是从C转过来的,从而没有使用异常,导致现在都16年了,php都出7了,我们在实际代码中还是没有使用异常,我前不久在项目中引入了异常,但也是简单的使用 try catch ,没有很多的经验,网上搜索也只是简单的一些使用例子,没有说在大型项目中怎么去使用,最近也是在读The Clean Architecture in php,深知代码组织的重要性,如果前期没有很好的组织好,后期的维护,重构代价都会很大,今天看到两篇文章:

Structuring PHP Exceptions

A Crash Course of Changes to Exception Handling in PHP 7

所以就有了本文。写这篇文章的目的是探讨一些在实际中怎么使用异常的方式,也希望得到大家的反馈,大家平时在开发中是怎么使用异常的?如何组织的。

为什么还使用异常?

在讨论使用异常之前,我们得统一认识:使用异常对项目是有益的。我们看看没有异常的时候,我们的处理方式。

返回错误号

functionfoo($arrInput){

if($arrInput['user_id']<0){

return-1;// 参数错误

}

// something else

}

当程序遇到错误时返回一个错误码,使用这种方式的好处是:我们每次在调用完函数后,都会检查返回值,当出现错误的时候,马上进行处理。

但是坏处也很明显:错误的处理和正常的业务逻辑耦合在了一起,我们平时开发中一个很恼人的感触就是:写一个业务逻辑,可能异常错误处理就占了2/3的代码,愁人啊,于是有人就发明了 异常

php中对错误的处理有两种,一种是errorwarnings,另一种是异常。

errors & warnings

php中的errorswarnings来源于过程式的代码,在过程式代码中,我们按照既定的步骤一步一步执行,此时如果出现了错误,我们必须要将程序的控制权接管过来,在PHP中是通过 set_error_handler 方法来设置处理函数的,但是这种方式没能提供一种有效的错误恢复手段,你可能除了打印下错误信息后,没有足够的错误发生时的上下文信息让你来恢复错误了。

exceptions

一般我们使用异常的代码如下:

try{

find_slash(string);

} catch(AnException& e) {

//Handle exception

}

这样做的好处是:程序逻辑和错误处理分离了。你可以看到函数是如何工作的,同时也可以看到失败时候是怎么处理的。另外,现在可以提供更多的异常发生的上下信息,帮助你从发生的异常中恢复出来。

举个例子:当从数据库中获取一条记录的时候发生了异常,我们可以根据异常的不同类型,采取不同的结果。如果异常时由于没有我们想要的id记录,我们可能返回一个 NullObject 是更好的方式,但如果异常是由于数据库连接的断开,我们可能会继续抛出异常,让异常被更上层的函数看到,因为这个异常在此处我们已经没有能够恢复的方法了。

通过SPL来构建异常

Standard PHP Library (SPL) 标准库中提供了一些 predefined set of exceptions,我们可以基于这些预定于的异常进行扩展,得到满足我们自己需求的代码。这样子做的好处是,我们能够很方便的捕获(catch)这些异常。

此处提供一个组织异常的方案: standard set of exception groupings 是一些预定义的异常,每次在使用的使用,通过 composer 引入。通过引入这一抽象层的目的是:让我能更好的区分想要捕获异常的粒度。

standard set of exception groupings 中的每个异常,都extendSPL中的异常,而且实现了 BrightNucleus\Exception\ExceptionInterface 接口,这么做可以方便我只捕获框架相关的异常,通过只捕获实现了接口的异常。

下面列举了捕获不同粒度的异常的方法:

Catch all exceptions

catch( Exception $exception ) {}

Catch all exceptions thrown by a Bright Nucleus library

catch( BrightNucleus\Exception\ExceptionInterface $exception ) {}

Catch a specific SPL exception (BrightNucleus or not)

catch( LogicException $exception )

Catch a specific SPL exception thrown by a Bright Nucleus library

catch( BrightNucleus\Exception\LogicException $exception ) {}

命名规范

目前命名的一个原则是:

该异常如果代表一个具体的错误,则使用一个过去时态的语句表名错误发生的原因

如果异常是一个基类,需要别的类进行扩展,则统一后缀 Exception

看一个具体的例子:

假设我们有一个功能是从文件中读取内容,可能会有3种错误发生:

文件名不合法

文件不存在

文件不可读

此时会有3种错误:

FileNameWasNotValid extends InvalidArgumentException

FileWasNotFound extends InvalidArgumentException

FileWasNotReadable extends RuntimeException

此时具体的错误都是过去式的句子,而基类都是带有统一后缀的。

通过构造函数捕获异常逻辑

我们一般在实例化异常的时候,都是直接在使用的时候才去new出来,但是这种方式导致异常的代码可能会比正常的业务逻辑还负责,非常不适合阅读,而且将相同的实例化逻辑放的到处都是,也符合代码重用的原则,我们举个例子:

publicfunctionrender( $view ){

if( !$this->views->has( $view ) ) {

$message = sprintf(

"The View "%s" does not exist.",

json_encode( $view )

);

thrownewViewWasNotFound( $message );

}

echo$this->views->get( $view )->render();

}

上面的代码中异常的处理逻辑比正常的业务逻辑还多,我们重构下,将异常的构建封装起来:

classViewWasNotFoundextendsInvalidArgumentException{

publicstaticfunctionfromView( $view, $code = null, Exception $previous = null ){

$message = sprintf(

"The View "%s" does not exist.",

json_encode( $view )

);

returnnewstatic( $message, $code, $previous );

}

}

我们可能会有多个构造函数,每个构造函数有不同的应用场景,此时我们再来写我们的 render 函数:

publicfunctionrender( $view ){

if( !$this->views->has( $view ) ) {

throwViewWasNotFound::fromView( $view );

}

echo$this->views->get( $view )->render();

}

现在代码就非常简洁了。

异常捕获

问:我们需要捕获什么异常?

答:只捕获当前上下文下能够处理的异常。

如果当前操作返回 NullObject ok,那在最外层套一个 catch( Exception $exception ) {} 就完全ok。但是如果当前操作只有正确才能保证后续操作继续,那你可能就需要捕获那些你当前能恢复的异常,那些不能恢复的异常,则让它往更上层去。

好了,今天就给大家讲这么多吧,喜欢我的内容可以关注或者分享(微信公众平台:tytedu)选择太原达内培训,不再孤军奋战,轻轻松松做IT高薪白领。太原达内培训带领有明确目标的学子迈向成功之路!

上一篇:PHP 踩坑实战第二坑:empty
下一篇:如何组织PHP中的异常(二)

PHP中十六个魔术方法详解(三)

JavaScript 与 Java、PHP 的比较

太原php培训资源站

太原PHP编程开发并发编程槽与坑

选择城市和中心
贵州省

广西省

海南省