课程咨询 :13623629309

太原PHP培训 > 达内新闻 > 如何组织PHP中的异常(一)
  • 如何组织PHP中的异常(一)

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

  • 如何组织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高级开发名企定制班(剩2个名额)

php高级开发名企定制班(剩2个名额)

开班日期:12-30

php高级开发周末班(剩5个名额)

php高级开发周末班(剩5个名额)

开班日期:12-30

php高级开发免费试听(剩5个名额)

php高级开发免费试听(剩5个名额)

开班日期:12-30

更多高级开发工程师精品班

更多高级开发工程师精品班

开班日期:12-30

  • 地址:山西省太原市小店区学府街长治路高新国际A座24层
  • 课程培训电话:13623629309     全国服务监督电话:400-827-0010
  • 服务邮箱 ts@tedu.cn
  • 2001-2016 达内国际公司(TARENA INTERNATIONAL,INC.) 版权所有 京ICP证08000853号-56

    在线客服系统