捕获异常时手动终止应用程序


Killing the application manually when catching a exception?

我有一个连接类,当我__construct它时,它会初始化数据库凭据。

每当失败时,它就会抛出一个异常,即由于文件为空或未设置变量而无法设置凭据。

变量现在尚未设置。

但我仍然可以调用该对象来调用该类中的其他函数,这是我不想要的,因为如果没有变量,这是不可能的。像这样:

$connection = new Connection(); //Causes exception because variables aren't set
$connection->initialize(); //Should not be ran, because the variables aren't set. Application shouldn't continue as well.
$connection->doFurtherThings(); //Which shouldn't be run as well, because the application couldn't go further without a db connection

当我捕捉到异常并且没有让值初始化时,出现这种情况的原因是什么?

public function __construct() {
    try {
        require "Configuration.php";
        $credentials = new Configuration('config.ini'); //Doesn't matter. just sets the configuration file
        $credential  = $credentials->getItems(); //Gets the items
        if (isset($credential['engine'], $credential['host'], $credential['dbname'], $credential['username'], $credential['password'])) {
            $this->engine   = $credential['engine'];
            $this->host     = filter_var($credential['host'], FILTER_VALIDATE_IP);
            $this->dbname   = $credential['dbname'];
            $this->username = $credential['username'];
            $this->password = $credential['password'];
        } else {
            throw new Exception("Login credentials aren't not set");
        }
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}

我必须自己在catch(Exception)内部die()应用程序吗?我以为是例外。

我明白你在做什么了。

您正在尝试使用配置对象。这太棒了,这正是你应该做的。然而,你怎么做并不是最好的方式。

获取使用配置对象的代码之前,您应该构建配置对象,并在尝试在另一个对象中使用该对象之前检查对象是否已全部设置并且是否有效。在这里,消费对象没有责任在系统上下文中验证来自内部对象的数据。

首先也是最重要的是,您的Credentials对象。我们在这里创建了一个接口,上面写着"任何Credentials对象都必须有一个validate()方法,如果凭据无效,该方法就会抛出一个异常

interface Credentials
{
   /**
    * @throws CredentialsValidationException
    */
    public function validate();
}

为什么有validate()方法?因为您不应该将业务逻辑放置在对象的构造函数中。未来的开发人员知道他们可以调用validate()将让他们知道对象是否具有有效凭据。

现在转到您的具体配置。在这个Configuration对象中,您声明:"要拥有一个有效的对象,用户必须提供主机、数据库名称、引擎、用户名和密码

class Configuration implements Credentials
{
    protected $host;
    protected $engine;
    protected $dbName;
    protected $username;
    protected $password;
   /**
    * We're NOT validating here, we're just stating that this object requires
    * these parameters to become an actual object
    */
    public function __construct($host, $engine, $dbName, $username, $password)
    {
        $this->host     = $host;
        $this->dbName   = $dbName;
        $this->engine   = $engine;
        $this->username = $username;
        $this->password = $password;
    }
   /**
    * As our Credentials interface requires, validate everything
    *
    * {@inheritDoc}
    */
    public function validate()
    {
        // Check this is a valid object
        // Consider using a Validation object passed in via Dependency Injection
        // If it's not a valid object, throw a CredentialsValidationException
    }
}

现在,我们已经将拥有有效凭据的责任转移到ConfigurationCredentials)对象本身中。下一步是实际使用这个对象。

class Connection
{
    protected $credentials;
   /**
    * @param Credentials $credentials
    */
    public  function __construct(Credentials $credentials)
    {
        $this->credentials = $credentials;
    }
}

Connection对象中,您声明需要实现Credentials接口的任何对象。因此,您不仅可以在这里使用多态性,还可以将应用程序配置与类解耦(这是您最初尝试做的)。

您现在也在使用依赖注入;通过构造函数/方法传入对象,以便由消费类使用。这意味着您的代码是解耦的,您可以在应用程序中的任何其他地方使用这些对象,或者如果您愿意,可以在完全不同的库中使用这些对象。

下面是您现在可以使用的对象API:

$credentials = new Configuration('host', 'engine', 'dbname', 'user', 'pass');
try
{
    $credentials->validate();
    $connection = new Connection($credentials);
    // @todo Whatever else you want to do
}
catch (CredentialsValidationException $e) 
{
    // @todo Log the error here with a logger object (check out monolog)
    // @todo Make sure the user viewing the code gets a nice response back
}

如果您想强制一个有效的Connection对象,只需在您正在使用的方法中调用Configuration::validate()(但不是构造函数)。您可以使用工厂来构建对象,并强制为您调用validate。做你喜欢的事!

关于死亡,不要在应用程序中死亡。做你需要做的事情,让调试的开发人员(你)和用户(你或其他人)以不同的方式知道哪里出了问题。通常,您会为开发人员登录并为用户提供消息。捕捉异常并反映问题所在。

结束语,这是一种方式。您可以使用Validation对象。您可以将validate()改为isValid(),然后只返回true/false。你可以让Connection对象调用validate()/isValid()-这取决于你的体系结构和你想做什么。关键是你已经解耦了这两个类,并同时使用了最佳实践。

最后一个想法——确保像我在代码中那样添加phpdocs。未来的开发人员不会想杀了你。我建议查看一个IDE,当你在代码中做了一些愚蠢的事情时,它会弹出一些小通知,比如phpstorm。

正如我在评论中所说,如何处理异常应该由调用者决定,而不是由被调用的类决定。

在构造函数中,如果出现问题,应该抛出异常,并让调用类决定如何处理该异常。

public function __construct() {
    require "Configuration.php";
    $credentials = new Configuration('config.ini'); //Doesnt matter. just sets the configuration file
    $credential  = $credentials->getItems(); //Gets the items
    if (isset($credential['engine'], $credential['host'], $credential['dbname'], $credential['username'], $credential['password'])) {
        $this->engine   = $credential['engine'];
        $this->host     = filter_var($credential['host'], FILTER_VALIDATE_IP);
        $this->dbname   = $credential['dbname'];
        $this->username = $credential['username'];
        $this->password = $credential['password'];
    } else {
        throw new Exception("Login credential's arent not set");
    }
}

现在,它的调用者决定在出现异常的情况下该怎么做,比如停止执行:

try {
    $connection = new Connection(); //Causes exception because variables arent set
    $connection->initialize(); //Should not be ran, because the variables arent set. Application shouldnt continue aswell.
    $connection->doFurtherThings();
} catch (Exception $e) {
    exit($e->getMessage()); // Login credential's arent not set
}

为了更好地说明这一点,我给您写了一个简单的例子,作为附带说明,您应该真正了解执行流是如何工作的。