首页 > 解决方案 > 使用自定义 ID 和自定义消息在 PHP 中处理错误

问题描述

我在一个项目中,我不想直接向用户抛出错误。相反,我想要为发生的错误定制消息。

以后我还需要保留一个错误编号,以便自定义来自类外部的错误消息,例如错误消息数组。

所以我在我设置的地方做了我自己的东西,$error = null然后将错误设置为一个后来成为消息的数字。

问题

这种方法有什么缺点吗?我是否更适合使用 try/catch 或其他方式?如果可能的话,我想保持代码简短而整洁。

在这个简短的代码示例中,错误处理似乎是该类的重要组成部分。在我几百行的真实代码中,它不是整个代码的很大一部分

http://sandbox.onlinephpfunctions.com/code/623b388b70603bf7f020468aa9e310f7340cd108

<?php
class Project {
  private $error = null;

  public function callMeFirst($num) {
    $this->nestedLevelOne($num);
    $this->nestedLevelTwo($num);
    $this->setResults();
  }

  public function callMeSecond($num) {
    $this->nestedLevelTwo($num);
    $this->setResults();
  }

  private function nestedLevelOne($num) {
    // Do stuff
    if($num !== 1) {
      $this->error = ['id' => 1, 'value' => $num];
    }
  }

  private function nestedLevelTwo($num) {
    // Do stuff
    if($num !== 20) {
      $this->error = ['id' => 2, 'value' => $num];
    }
  }

  private function message($args) {
    extract($args);

    $message = [
      1 => "Nested level one error: $value",
      2 => "Another error at level two: $value",
    ];

    return ['id' => $id, 'message' => $message[$id]];
  }

  private function setResults() {
    $results['success'] = ($this->error === null) ? true : false;

    if($this->error !== null) {
      $results['error'] = $this->message($this->error);
    }
    $this->results = $results;
  }
}

$project = new Project();
$project->callMeFirst(1);
$project->callMeFirst(2);

print_r($project->results);

它会输出

Array
(
  [success] => 
  [error] => Array
    (
      [id] => 2
        [message] => Another error at level two: 2
    )
)

我问的原因是我有一种感觉,在这种情况下我可能会重新发明轮子。我是吗?

如果有更好的解决方案,我会很高兴看到该代码的样子。

标签: phpclasserror-handling

解决方案


I would probably separate the business logic from the error handling to simplify each part more. By using exceptions, you keep your business logic simpler; you simply throw an exception whenever you encounter a case that is not permitted, thereby preventing getting into any sort of inconsistent state at all. The business logic class doesn't have to care about how this error will be processed further, it just needs to raise the error. You should then create a separate wrapper around that business logic class which simply cares about handling any errors and formatting them into an array or other sort of response which will be handled elsewhere. Something along these lines:

class ProjectException extends Exception {
    public function __construct($num) {
        parent::__construct(get_called_class() . ": $num");
    }
}

class NestedLevelOneException extends ProjectException {
    // customise __construct here if desired
}

class NestedLevelTwoException extends ProjectException {}

class Project {
    public function callMeFirst($num) {
        $this->nestedLevelOne($num);
        $this->nestedLevelTwo($num);
    }

    public function callMeSecond($num) {
        $this->nestedLevelTwo($num);
    }

    protected function nestedLevelOne($num) {
        if ($num !== 1) {
            throw new NestedLevelOneException($num);
        }

        // do stuff
    }

    protected function nestedLevelTwo($num) {
        if ($num !== 20) {
            throw new NestedLevelTwoException($num);
        }

        // do stuff
    }
}

class ProjectService {
    protected $project;

    public function __construct(Project $project = null) {
        $this->project = $project ?: new Project;
    }

    public function process($a, $b) {
        try {
            $this->project->callMeFirst($a);
            $this->project->callMeSecond($b);
            return ['success' => true];
        } catch (ProjectException $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
}

$api = new ProjectService;
print_r($api->process(1, 2));
  1. By defining three separate exceptions, you get a lot of flexibility in how and where you want to handle errors. You can specifically catch NestedLevel*Exception, or you catch either of them with ProjectException.

  2. By having your methods throw exceptions, you gain flexible error handling possibilities. You are free to not catch the exception and have the program terminate, as would be entirely reasonable if one of your business requirements isn't met. Alternatively, you can catch the exception at a level up that is prepared to deal with that error and turn it into something meaningful that can be acted upon.

  3. By moving the generation of the error message into the exceptions, you keep the error type and its message self-contained. There's exactly one place where you define what kind of error may happen and what its error message will look like; instead of spreading that out over your entire codebase. And you're still free to choose some other error message in the UI, e.g. for localising different kinds of errors into multiple languages; just check the type of the exception object.

  4. By using a separate ProjectService which cares about handling those exceptions and turning it into an array response, you narrow each class' responsibilities and make each class more flexible and simpler.


推荐阅读