PHP pdo 实例作为私有静态属性


PHP pdo instance as private static property

我正在尝试基于 OOP 设计我的网站,但我在如何设计数据库连接方面遇到了麻烦。目前,我正在抽象类连接器中创建私有静态PDO对象。显然,任何需要与数据库交互的东西都会扩展这个类。我一直在反复讨论如何确保脚本中只有一个连接或 PDO 对象,因为某些页面将需要多个扩展连接器的类。许多人似乎为此目的推荐单例模式,但我目前的做法似乎完成了同样的事情。

这是我当前的代码。

abstract class Connector
{
    private static $dbh;
    public function __construct()
    {
        try
        {
            self::$dbh = new PDO(...);
            self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        catch(PDOException $e)
        {
            die($e->getMessage());
        }
    }
    public function getDB()
    {
        return self::$dbh;
    }
}

然后任何子类都会像这样使用它。

class Subclass extends Connector
{
    public function interactWithDB()
    {
        $stmt = $this->getDB()->prepare(...);
        // etc...
    }
}

从理论上讲,我认为子类的每个实例都应该始终访问相同的PDO实例。这段代码是否真的有意义,或者我是否以某种方式误解了静态属性?是糟糕的设计/实践,还是单例有更多的优势?

如果有什么不清楚的地方发表评论,谢谢!

编辑:

连接器类的存在不仅仅是为了保存 PDO 对象。它的析构函数关闭连接(使其为空),并且它包含诸如isValueTaken之类的函数,该函数检查数据库中是否已存在值。它具有以下抽象函数

abstract function retrieveData();
abstract function setData();

例如,我有一个扩展连接器的用户类。它定义了 setData() 以在数据库中注册用户。我不知道这是否会对反应产生影响。

显然,任何需要与数据库交互的东西都会扩展这个类。

从 OOP 的角度来看,这真的毫无意义。当某个类扩展另一个暗示"是"关系的类时。如果你走这条路,你将很难不违反OCP,这是SOLID中的字母之一。

我一直在反复讨论如何确保脚本中只有一个连接或 PDO 对象,因为某些页面将需要多个扩展连接器的类。

容易!只需创建一个实例。

许多人似乎为此目的推荐单例模式,但我目前的做法似乎完成了同样的事情。

许多这样的人对 OOP 原理一无所知。使用单例只会引入一个"花哨的"全局实例/状态。

这段代码是否真的有意义,或者我是否以某种方式误解了静态属性?

老实说,这更多的是对OOP的误解。

是糟糕的设计/实践,还是单例有更多的优势?

见上文。


您应该做的(在 OOP 中)是将数据库连接注入到需要它的类中。这使得你的代码松散耦合,反过来又使你的代码更好地可维护、可测试、可调试和灵活。

我也不太明白为什么你需要为 pdo 连接创建一个数据库类,因为 PDO API 本身已经是 OOP。因此,除非您有真正的理由为 PDO 编写适配器(可能是这种情况,因为有一些),否则我会放弃它。

我的€0.02

--

为了响应您的编辑:

连接器类的存在不仅仅是为了保存 PDO 对象。它的析构函数关闭连接(使其为空)。

通常根本不需要关闭连接。处理请求后,连接将自动关闭(除非我们谈论的是持久连接)。

它包含诸如isValueTaken之类的函数,该函数检查值是否已在数据库中。它具有以下抽象函数

这听起来像是另一个班级的工作。

例如,我有一个扩展连接器的用户类。它定义了 setData() 以在数据库中注册用户。我不知道这是否会对反应产生影响。

不,我的观点仍然成立。用户无需从数据库继承。这听起来是不是很奇怪。从数据库继承的用户(我不想遇到那个人)。如果需要,应将数据库连接注入用户。

前言

单例方法通常不受欢迎。你会被猛禽盯上。


您实质上要问的是如何配置连接并使其全局可用。这通常称为全局状态。可以使用容器类和静态方法实现此目的。

下面是一个示例

namespace Persistence'Connection'Config;
interface PDOConfig {
    public function getDSN();
    public function getUsername();
    public function getPassword();
    public function getDriverOptions();
}
class MySqlConfig implements PDOConfig {
    private $username;
    private $password;
    private $db;
    private $host = 'localhost';
    private $charset = 'utf8';
    public function __construct($username, $password, $db) {
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
    }
    // getters and setters, etc
    public function getDSN() {
        return sprintf('mysql:host=%s;dbname=%s;charset=%s',
            $this->host, $this->db, $this->charset);
    }
    public function getDriverOptions() {
        return [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ];
    }
}
namespace Persistence'Connection;
use Persistence'Connection'Config'PDOConfig;
class Registry {
    private static $connection;
    private static $config;
    public static function setConfig(PDOConfig $config) {
        self::$config = $config;
    }
    public static getConnection() {
        if (self::$connection === null) {
            if (self::$config === null) {
                throw new RuntimeException('No config set, cannot create connection');
            }
            $config = self::$config;
            self::$connection = new 'PDO($config->getDSN(), $config->getUsername(),
                $config->getPassword(), $config->getDriverOptions());
        }
        return self::$connection;
    }
}

然后,在应用程序执行周期的某个时间点(早期)

use Persistence'Connection'Config'MySqlConfig;
use Persistence'Connection'Registry;
$config = new MySqlConfig('username', 'password', 'dbname');
Registry::setConfig($config);

然后,您可以使用

Registry::getConnection();

代码中用于检索 PDO 实例的任何位置。

如果有人正在阅读本文,只是一个旁注,如果有人正在使用 Phil 的上述代码片段,请记住在 getDriverOptions() 中在 PDO 前面使用黑色斜杠,以便引用全局命名空间。 它应该看起来像这样。

public function getDriverOptions() {
      return [
          'PDO::ATTR_ERRMODE => 'PDO::ERRMODE_EXCEPTION,
          'PDO::ATTR_EMULATE_PREPARES => false,
          'PDO::ATTR_DEFAULT_FETCH_MODE => 'PDO::FETCH_ASSOC
      ];
  }