课程咨询 :13623629309

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

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

  • 如何组织PHP中的异常(二)

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

    SPL中,我们定义了两大类异常:

    Logic exceptions

    逻辑异常是那些由于开发者的错误而导致的异常。你可能在请求一些不存在的值,或者调用传递的参数不对等等。这些异常在开发中都需要我们马上处理掉的。在理想情况下,这些逻辑异常在实际生产系统中是不应该出现的。

    runtime exception

    运行时异常是一些在开发中不能控制的异常,如:数据库链接的异常断开,文件的读写权限不对等等。这些错误是无法避免的,我们不可能开发一个没有错误的系统,我们能做得只是当这些错误发生的时候,尽快的去通知系统管理员,而不是代码出现 fatal

    这就是为什么我们在开发中需要在某一软件层捕获运行时错误,而对于逻辑错误,我们尽可能让它在开发时就让他们暴露出来,好让我们在开发时就解决它。

    中心化的Error处理函数

    我们将逻辑异常都pass through,没有去捕获,那么作为一个web应用,我们不能让用户无响应啊,因此我们需要通过一个中心化的处理函数来捕获所有我们没有处理的异常。

    捕获后,我们一般的工作是:记录这些异常,记录调用栈,方便我们去分析解决这些问题。

    对于这个工作,我推荐使用 BooBoo 来做。

    总结

    此处总结下我们的原则:

    对于运行时异常,我们尽量捕获然后进行处理,重要的上报错误,让管理员知道系统异常,而对于逻辑异常我们则是将其尽可能详细的记录下来,因为这些错误理论上是不应该出现在生产环境中。

    我们在捕获异常的时候,只捕获在该层级能处理的异常,对于不能处理的则让它到上一层上去。

    我们需要一个全局的异常处理函数,处理如返回htmljson这种格式问题,以及处理错误信息的转换(隐藏系统内部错误信息),错误的记录,现场环境的保存等公共逻辑。

    一个示例

    讲了这么多,还是那句话

    talk is cheap, show me the code

    我们基于的一个基本代码是:

    $user = $this->usersGateway->fetchOneById($userId);

    if(!$user) {

    thrownewException('User with the ID: '. $userId .' does not exist');

    }

    用户定义异常

    上面针对找不到user的情况,我们只是简单的抛出了异常。但是上面的问题是:仅仅抛出异常不足以帮助我们定位问题,单一的异常类型,不能让我们针对不同的类型做出不同行为,因此解决方法是自定义异常。

    classUserNotFoundExceptionextendsRuntimeException

    {

    }

    //...

    thrownewUserNotFoundException('User with the ID: '. $userId .' does ot exist');

    格式化异常

    现在我们已经有了异常类,并且异常的生成和异常消息都是异常类本身的职责,因此我们根据单一职责(SRP)将其组织到异常类中:

    classUserNotFoundExceptionextendsRuntimeException

    {

    publicstaticfunctionforUserId(string $userId):self

    {

    returnnewself(sprintf(

    'User with the ID: %s does not exist',

    $userId

    ));

    }

    }

    在使用异常的地方我们简单的调用下面的代码:

    throwUserNotFoundException::forUserId($userId);

    聚合异常

    根据单一职责(SRP)我们将相同异常放到一起,不同的功能拆分出来,看例子:

    classUserExceptionextendsException

    {

    publicstaticfunctionforEmptyEmail():self

    {

    returnnewself("User's email must not be empty");

    }

    publicstaticfunctionforInvalidEmail(string $email):self

    {

    returnnewself(sprintf(

    '%s is not a valid email address',

    $email

    ));

    }

    publicstaticfunctionforNonexistentUser(string $userId):self

    {

    returnnewself(sprintf(

    'User with the ID: %s does not exist',

    $userId

    ));

    }

    }

    在上面的例子中,异常类 UserException 有两个功能,第一个负责User的验证异常,另一个则是没有用户的异常,因此我们应该拆分为两个:

    classInvalidUserExceptionextendsDomainException

    {

    publicstaticfunctionforEmptyEmail():self

    {

    returnnewself("User's email address must not be empty");

    }

    publicstaticfunctionforInvalidEmail(string $email):self

    {

    returnnewself(sprintf(

    '%s is not a valid email address',

    $email

    ));

    }

    }

    classUserNotFoundExceptionextendsRuntimeException

    {

    publicstaticfunctionforUserId(string $userId):self

    {

    returnnewself(sprintf(

    'User with the ID: %s does not exist',

    $userId

    ));

    }

    }

    此时我们就能针对不同的异常类采取不同的措施,可能我们会根据异常类返回合适的 HTTP status codes

    异常代码

    异常的构造函数接受code作为第二个参数,所以我们可以通过不同的错误码来标志不同的错误。

    classUserNotFoundExceptionextendsRuntimeException

    {

    publicstaticfunctionforUserId(string $userId):self

    {

    returnnewself(

    sprintf(

    'User with the ID: %s does not exist',

    $userId

    ),

    ErrorCodes::ERROR_USER_NOT_FOUND

    );

    }

    }

    我们会将所有的错误码都放到一个文件中,方便管理。

    组件级别的异常

    当我们提供一个库给别人使用的时候,我们可能希望能够捕获我们库级别的异常,这通过一个模式 Marker Interface 可以实现:

    namespaceApp\Domain\Exception;

    interfaceExceptionInterface

    {

    }

    classUserNotFoundExceptionextendsRuntimeExceptionimpementsExceptionInterface

    {

    publicstaticfunctionforUserId(string $userId):self

    {

    returnnewself(

    sprintf(

    'User with the ID: %s does not exist',

    $userId

    ),

    ErrorCodes::ERROR_USER_NOT_FOUND

    );

    }

    }

    我们通过在每个命名空间都声明一个 ExceptionInterface 类来实现,这样我们就可以通过代码 try{}catch(ExceptionInterface $e){} 来捕获所有本库的错误。

    错误处理

    上代码:

    classUserControllerextendsBaseController

    {

    publicfunctionviewUserAction(RequestInterface $request)

    {

    try{

    $user = $this->userService->get($request->get('id'));

    returnnewJsonResponse($user->toArray());

    } catch(\Exception$ex) {

    returnnewJsonResponse([

    'error'=> $ex->getCode(),

    'message'=> $ex->getMessage(),

    ], 500);

    }

    }

    }

    上面的处理中,我们在controller中通过一个最外层的 try{}catch{} 捕获了所有异常,但是我们针对不同的需求可能会有不同的返回格式的要求,可能我们需要针对参数的不同返回html或者json格式,另外我们也不希望底层的错误信息,如:数据库连接失败,这样子的错误信息直接返回给调用方,那怎么解决呢?

    这就要用到PHP的全局错误处理函数了,通过 set_error_handler 来设置,另外推荐除了 BooBoo 另外一个开源库: Whoops ,能很好的解决这个问题。

    你的观点

    相信你在实际工作中肯定也遇到过好多类似的困扰,你在实际工作中也有你自己的一套解决方案,期待你的分享,让更多的人知道好的优秀的方案,所以期待你在评论区写下你的方案。

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

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

下一篇:PHP5 升级到PHP7时curl注意事项

最新开班日期  |  更多

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

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

开班日期:12-29

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

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

开班日期:12-29

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

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

开班日期:12-29

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

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

开班日期:12-29

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