由于 PDOStatement::execute 返回 true/false,并且您当前的 run 方法在成功时返回 PDOStatement,在失败时返回 false。我建议检查prepare 和execute 是否为假,如果成功则返回PDOStatement,否则返回false,PDO::prepare 和PDO::query 就是这种情况。
/**
* @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
另一种方法是将最后执行的值存储在属性中,以供以后检索。
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 中导致异常的函数名称。
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');
}
}
您现在将能够处理任何特定类型的每个异常。
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