如何 return 在 PDO 包装器中执行的值

How to return the value of execute in PDO wrapper

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

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

但是,正如您在包装器中看到的那样,运行 命令仅 returns 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;
    }

}

?>

因为 PDOStatement::execute returns true/false 并且您当前的 run 方法是 returning a PDOStatement 成功和 false .我建议检查 prepareexecute 是否为 false 和 return PDOStatement 是否成功,否则为 false,就像 PDO::prepare 和 [=29= 的情况一样].

函数示例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

解决以下 best-practices 关于使用异常处理确定 PDO::execute 方法何时从 Database::run 方法中具体失败的问题的评论。

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

Generally speaking StackOverlow is not an appropriate place to discuss or evaluate an author's application of best-practices. Those types of discussions or critiques should be reserved for CodeReview. Whosebug is intended to answer the author's specific question, or provide a viable alternative method to accomplish what the user is asking for. Not infer the user has asked the wrong question.

要使用例外,您需要启用 PDO::ERRMODE_EXCEPTION(见下文 Database class)。

try/catch 与 PDO 包装器方法一起使用的问题是 PDO 只会抛出一个异常对象 PDOException,这无法让您确定调用哪个 PDO 方法特别失败。留给您阅读 PDOException::getMessage() or 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;
}

Please see the answer on PDO mysql: How to know if insert was successful by Your Common Sense for a more generalized approach to PDOException handling.

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

为了捕获任何 run 特定异常,可以使用一个空接口,然后在扩展 PDOException classes.

上实现该接口
interface DatabaseRunException{}

然后为您要处理的每个特定 PDO 方法创建一个新异常 class,实现 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+ 进行了一些 best-practices 和优化更改。

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 <= 7.0 中可以使用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