【问题标题】:How to return the value of execute in PDO wrapper [duplicate]如何在 PDO 包装器中返回执行的值 [重复]
【发布时间】:2019-10-18 01:11:34
【问题描述】:

下面是我的 PDO 包装器。我希望能够使用run 方法但是,我希望能够检查执行是否成功,例如:

if($sth->execute())
{
   ...
}

但是,正如您在包装器中看到的那样,运行命令仅返回 prepare 语句,实现此目的最有效的方法是什么?

<?php

class Database {

    const hostname = 'localhost';
    const user = 'root';
    const password = '';
    const charset = 'utf8';
    const database = 'syn_v2';

    protected static $instance;
    protected $pdo;

    protected function __construct()
    {
        $opt = array(
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
            PDO::ATTR_EMULATE_PREPARES => false
        );

        $dsn = sprintf('mysql:host=%s;dbname=%s;charset=%s', self::hostname, self::database, self::charset);

        $this->pdo = new PDO($dsn, self::user, self::password);
    }

    public static function instance()
    {
        if(self::$instance === null)
        {
            self::$instance = new self;
        }

        return self::$instance;
    }

    public function __call($method, $args)
    {
        return call_user_func_array(array($this->pdo, $method), $args);
    }

    public function run($sql, $args = [])
    {
        if(!$args)
        {
            return $this->query($sql);
        }

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($args);

        return $stmt;
    }

}

?>

【问题讨论】:

  • @YourCommonSense 这不是引用问题的重复。作者已经知道他们想要execute 的返回响应以及为什么。 OP 面临的问题是确定如何在从另一个对象内部调用 execute 的结果后检索它。 IE。设置属性值,直接返回等
  • @fyrye 关闭的关键不是问题而是答案。有处理这种情况的答案。在这种罕见的情况下,当你需要检查结果时,你必须捕获异常
  • 这不是重复的
  • 这里的问题是“我希望能够检查是否执行成功”,这甚至与复制源几乎相同的措辞。我看不出它不是重复的。
  • @YourCommonSense 您的答案或该页面上的任何答案具体如何允许 OP 在调用Database::run()明确确定Database::run() 是成功还是失败?您已经推断出适合您认为 OP 应该寻找的意图。请参阅我更新的带有异常处理的答案以进行澄清。

标签: php pdo return prepared-statement


【解决方案1】:

由于 PDOStatement::execute 返回 true/false,并且您当前的 run 方法在成功时返回 PDOStatement,在失败时返回 false。我建议检查prepareexecute 是否为假,如果成功则返回PDOStatement,否则返回false,PDO::preparePDO::query 就是这种情况。

功能示例https://3v4l.org/OnDdn

/**
 * @return PDOStatement|false
 */
public function run($sql, $args = [])
{
    if (!$args) {
        return $this->pdo->query($sql);
    }
    if ($stmt = $this->pdo->prepare($sql)) {
        if ($stmt->execute($args)) {
            return $stmt;
        }
    }

    return false; //either prepare or execute failed
}
$db = Database::instance();
var_dump($db->run('SELECT ?', ['foo', 'bar'])); //false

另一种方法是将最后执行的值存储在属性中,以供以后检索。

示例https://3v4l.org/UbM1N

class Database
{

    protected $lastExecute;

   //...

    /**
     * @return PDOStatement|false
     */
    public function run($sql, $args = [])
    {
        if (!$args) {
            return $this->pdo->query($sql);
        }
        if ($stmt = $this->pdo->prepare($sql)) {
            $this->lastExecute = $stmt->execute($args);
        }

        return $stmt;
    }

    /**
     * @return null|bool
     */
    public function getLastExecute()
    {
       return $this->lastExecute;
    }
}
$db = Database::instance();
$db->run('SELECT ?', ['foo', 'bar']);
var_dump($db->getLastExecute()); //false

解决以下最佳实践 cmets 关于使用异常处理确定 PDO::execute 方法何时在 Database::run 方法内具体失败的问题。

请记住Best-Practices 不是关于对错,“它们只是推荐的编写代码的方法。”指在专业应用程序开发中通常首选的一种编程方法。始终使用最适合您、您正在开发的环境和您的应用程序要求的方法。

一般而言,StackOverlow 不适合讨论或评估作者的应用 最佳实践。这些类型的讨论或批评应保留给CodeReview。 StackOverflow 是 旨在answer the author's specific question,或提供可行的替代方法来完成用户的要求。不推断用户提出了错误的问题。

要使用异常,您需要启用PDO::ERRMODE_EXCEPTION(请参阅下面的Database 类)。

try/catch 与 PDO 包装方法一起使用的问题在于,PDO 只会抛出一个 Exception 对象PDOException,这无法让您确定哪个 PDO 方法调用具体失败了。让您阅读PDOException::getMessage()PDOException::getTrace(),以确定原因。

一种简单的方法是检查 PDOException::trace 中导致异常的函数名称。

功能示例(PHP 5.6+):https://3v4l.org/fDnBI

try {
   $db = Database::instance();
   var_dump($db->run('SELECT ?', ['foo', 'bar'])->fetch());
} catch(\PDOException $e) {
   if ('execute' === $e->getTrace()[0]['function']) {
       echo 'PDO::execute() failed';
       //Handle the execute exception
   }
   throw $e;
}

请在Your Common Sense 上查看PDO mysql: How to know if insert was successful 的答案,了解PDOException 的更通用方法 处理。

上述方法阻止您在Database::run 方法中仅处理特定的异常类型,当异常意外时,要求您在条件之后使用throw $e;
为了解决这个问题,另一种方法是创建自定义异常类。您可以通过扩展PDOException 基类来实现这一点,使其与其他异常处理方法兼容或捕获其中任何一种。

为了捕获任何run 特定异常,可以使用一个空接口,然后在扩展的PDOException 类上实现该接口。

interface DatabaseRunException{}

然后为您要处理的每个特定 PDO 方法创建一个新的异常类,该类实现 DatabaseRunException 接口。

class PDOPrepareException extends PDOException implements DatabaseRunException{}

class PDOExecuteException extends PDOException implements DatabaseRunException{}

class PDOQueryException extends PDOException implements DatabaseRunException{}

要使用自定义异常来确定哪个 PDO 方法失败,您需要处理 Database::run() 方法中的 PDOException(s)throw 自定义异常之一。
为简洁起见,我删除了某些部分,注释掉了会改变您当前配置的内容,对 PHP 5.6+ 进行了一些最佳实践和优化更改。

class Database 
{

    private const OPTIONS = [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        // PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
        // PDO::ATTR_EMULATE_PREPARES => false
    ];

    //...

    protected function __construct()
    {
        $this->pdo = new PDO($dsn, self::user, self::password, self::OPTIONS);
    }

    public static function instance()
    {
        if (null === self::$instance) {
            self::$instance = new self;
        }

        return self::$instance;
    }

    public function __call($method, $args)
    {
        //always ensure the desired method is callable!
        if (is_callable([$this->pdo, $method])) {
            //php 5.6+ variadic optimization (aka splat operator)
            return $this->pdo->$method(...$args);

            //php <= 5.5
            //return call_user_func_array(array($this->pdo, $method), $args);
        }
        throw new \BadMethodCallException(sprintf('Unknown method PDO::%s called!', $method));
    }

    public function run($sql, $args = [])
    {
        if (!$args) {
            try {
                return $this->query($sql);
            } catch(\PDOException $e) {
                 throw new \PDOQueryException($e->getMessage(), (int) $e->getCode(), $e);
            }
        }
        try {
            $stmt = $this->prepare($sql);
        } catch(\PDOException $e) {
            throw new \PDOPrepareException($e->getMessage(), (int) $e->getCode(), $e);
        }
        try {
            $stmt->execute($args);

            return $stmt;
        } catch(\PDOException $e) {
            throw new \PDOExecuteException($e->getMessage(), (int) $e->getCode(), $e);
        }

        throw new \LogicException('Unknown error occurred');
    }

}

功能示例(PHP 5.6+):https://3v4l.org/8QoRF

您现在将能够处理任何特定类型的每个异常。

try {
   $db = Database::instance();
   $db->run('SELECT ?', ['foo', 'bar']);
} catch(\PDOExecuteException $e) {
    echo 'PDO::execute() failed';
    //Handle the execute exception
    throw $e;
}

在 PHP 7.1+ 中,您可以捕获多个异常。

try {
   $db = Database::instance();
   $db->run('SELECT ?', ['foo', 'bar']);
} catch(\PDOQueryException $e) {
    //Handle the query exception
    throw $e;
} catch(\PDOPrepareException $e) {
    //Handle the prepare exception
    throw $e;
} catch(\PDOExecuteException $e) {
    echo 'PDO::execute() failed';
    //Handle the execute exception
    throw $e;
}

在 PHP DatabaseRunException 接口捕获并检查由Database::run()instanceof 引起的特定异常,以确定实际抛出的异常。

try {
   $db = Database::instance();
   $db->run('SELECT ?', ['foo', 'bar']);
} catch(\DatabaseRunException $e) {
    if ($e instanceof \PDOQueryException) {
       //Handle the query exception
    } elseif ($e instanceof \PDOPrepareException) {
       //Handle the prepare exception
    } elseif ($e instanceof \PDOExecuteException) {
       echo 'PDO::execute() failed';
       //Handle the execute exception
    }
    throw $e;
}

如您所见,这会增加代码复杂性,并由您决定最适合您的应用程序需求的内容。

需要注意的是,如果在声明之前发生异常,在try 部分中声明的变量将不会被声明。

try {
  throw new \Exception('FooBar');
  $foo = 'foo';
} catch(\Exception $e) {
   var_dump(isset($foo)); //false
}
var_dump(isset($foo)); //false

【讨论】:

  • 哇哦,效果很好。出于某种奇怪的原因,我从没想过这样做。谢谢。
  • @Intel 这与您目前的情况完全相同。这意味着您的初始代码已经“完美运行”。
  • 此外,一般来说这是一个糟糕的架构,让你的函数返回不同类型的结果。您的函数不应在错误时返回 false - 这就是 exceptions 的用途
  • @YourCommonSense 虽然我同意您对异常的评估,尤其是对数据库查询的评估,并且几乎写了相同的内容,但看到PDO 未设置为PDO::ERRMODE_EXCEPTION,您对作者的原始代码不正确,它从prepare 返回$stmt 而不是execute,意思是prepare 可能成功,但execute 可能会失败,恕不另行通知。此外,preparequery 在失败时都返回 PDOStatementfalse,因此 run 方法的功能类似于默认情况下各个 PHP 函数的功能。
  • 哇。确实。你可以看出它更有可能是一个错字。因此,@Intel 看起来您想要修复它,而不是在不良做法的道路上费尽心思。
猜你喜欢
  • 2011-12-01
  • 2017-03-30
  • 1970-01-01
  • 2018-09-08
  • 2020-05-17
  • 1970-01-01
  • 2018-11-07
  • 1970-01-01
相关资源
最近更新 更多