3

我正在尝试基于 OOP 设计我的网站,但是我在如何设计数据库连接方面遇到了麻烦。目前,我正在一个抽象类 Connector 中创建一个私有静态 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() 在数据库中注册用户。我不知道这是否会影响响应。

4

3 回答 3

7

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

从 OOP 的角度来看,这确实没有意义。当某个类扩展另一个类时,这意味着“是”关系。如果你走这条路,你将很难不违反OCP,这是SOLID中的字母之一。

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

简单的!只需创建一个实例。

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

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

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

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

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

看上面。


您应该做的(在 OOP 中)是将数据库连接注入到需要它的类中。这使您的代码松散耦合,从而使您的代码具有更好的可维护性、可测试性、可调试性和灵活性。

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

我的 €0.02

--

回应您的编辑:

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

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

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

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

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

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

于 2013-11-07T23:11:15.560 回答
3

前言

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


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

这是一个例子

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 实例。

于 2013-11-07T23:15:58.063 回答
1

如果有人正在阅读本文,请注意,如果有人正在使用 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
      ];
  }
于 2017-05-08T08:15:25.057 回答