【问题标题】:Killing the application manually when catching a exception?捕获异常时手动终止应用程序?
【发布时间】:2014-11-27 11:11:51
【问题描述】:

我有一个连接类,当我__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() 应用程序吗?我认为例外是这样做的。

【问题讨论】:

  • 所以你不希望 ->getItems 被调用,对吗?
  • @Coulton 不,我不希望应用程序可以走得更远。无法初始化数据库变量时。其中包括我不能打电话给$connection->initialize()
  • 如何处理异常由实现者决定,所以不要在构造函数中捕获异常。
  • @AlexandruG。我不明白你的意思,当数据库应该为用户初始化时,它不起作用。该应用程序不应该走得更远。
  • 好的,所以将exit(); 添加到您的异常中

标签: php oop exception


【解决方案1】:

我明白你在做什么。

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

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

首先,您的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
    }
}

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

class Connection
{
    protected $credentials;

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

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

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

这是您现在可以使用的对象 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

【讨论】:

  • @Jimbo 首先,感谢您抽出宝贵时间 :) 我一直在阅读您的答案,看起来非常好。唯一的一点就是它高于我的水平,我几乎没有 OOP 经验。所以我有几个问题,验证方法到底是做什么的?它是一个布尔值吗?在这种情况下,我在哪里检查例如数据库在哪里打开?我必须声明新对象还是可以使用现有的一次?如果我的英语不好,我很抱歉,我目前正在手机上输入这个。
  • 验证方法是检查字符串是否为空,以及它们是否在给定参数等范围内(例如主机是带有filter_var() 的IP 地址)。例如,在执行其他所有操作之前,您可以检查每个方法中打开数据库的位置 ($this->checkConnected();)。
  • @Jimbo 和$this->checkConnected(); 里面是否有某种 if 语句,如果失败,你会抛出异常?
  • @Bas 我猜你会有一些代码来确保连接正常。如果没有,你可能会抛出一个ConnectionException
  • @Jimbo 好的,最后一个问题。很抱歉我花了这么长时间,但如果我要在 Connection 类中创建另一个函数,它不需要任何参数。如何访问每一个凭据?
【解决方案2】:

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

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

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
}

为了更好地说明这一点,我给你写了一个simple example 作为旁注,你应该真正了解执行流程的工作原理。

【讨论】:

  • 谢谢,但我不太喜欢这个解决方案。初始化值是必需的。
  • @Bas 你是什么意思?
  • 初始化变量几乎是必须的。如果它们不存在,则该页面不应再进一步。因为它无法加载它的数据
  • @Bas 因为Connection类会在构造函数中直接抛出异常,所以$connection->initialize();出现异常不会执行。
  • 你是什么意思?当你实例化Connection(); 对象时?
猜你喜欢
  • 2019-01-24
  • 1970-01-01
  • 2015-08-20
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多