【问题标题】:PHP pdo instance as private static propertyPHP pdo 实例作为私有静态属性
【发布时间】:2013-11-19 19:59:26
【问题描述】:

我正在尝试基于 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 实例。这段代码是否真的有意义,或者我是否以某种方式误解了静态属性?是不好的设计/实践,还是 Singleton 有更多的优势?

如果有不清楚的地方请评论,谢谢!

编辑:

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

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

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

【问题讨论】:

  • “显然,任何需要与数据库交互的东西都会扩展这个类。” ---一点都不明显。你需要用勺子,你不需要成为勺子,对吗?提示:google 依赖注入和委托。 PS:pimple.sensiolabs.org
  • 我建议改用this approach 之类的东西。
  • @zerkms pimple 实现了一个服务提供者,这是另一种反模式。并且服务提供者不是 DI容器。
  • @tereško:周围有任何“真正的”开源 php DI 容器可用吗?
  • @tereško:我确实意识到了不同之处。我可以忍受 sf2 服务容器的“限制”

标签: php database pdo static


【解决方案1】:

如果有人正在阅读本文,请注意,如果有人使用 Phil 的上述代码 sn-p,请记住在 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
      ];
  }

【讨论】:

    【解决方案2】:

    前言

    单例方法通常不受欢迎。你将成为set upon by raptors


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

    这是一个例子

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

    【讨论】:

    • “我知道有时单例是一个肮脏的词,但无论如何,你需要某种全局状态。你也可以使用注册表模式,但这完全取决于你。”呜?没有也没有。
    • @PeeHaa OP 询问如何配置全局状态。查看任何流行的框架(而不仅仅是 PHP),您会看到某种形式的静态连接池/注册表。否则,您需要实现服务定位器或 DI 容器,这是一项更大的任务
    • OP 正在询问 OOP。单例和注册表都与 OOP 无关。是的,你是对的,大多数框架也与 OOP 没有太大关系。
    • @Phil 带有“故意”的静态注册表是单例。
    • @tereško 我想说它不是一个单例,而是一个静态的、延迟加载的实例化器。然后我意识到,这实际上是一个单身人士:)
    【解决方案3】:

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

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

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

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

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

    许多这样的人对 OOP 原则一无所知。使用单例只会引入“花哨”global instance / state

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

    说实话这更像是对OOP的误解。

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

    见上文。


    你应该做的(在 OOP 中)是 inject the database connection 进入需要它的类。这使您的代码松散耦合,从而使您的代码具有更好的可维护性、可测试性、可调试性和灵活性。

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

    我的 €0.02

    --

    响应您的编辑:

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

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

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

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

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

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

    【讨论】:

    • 我编辑了我的问题以包含更多信息。如果您的答案仍然适用,那么保留抽象类是否有意义,但将 PDO 对象作为参数传递给任何需要它的函数,而不是将其作为属性保留?
    • 除了在依赖注入需要时尝试将 PDO 实例保持在范围内之外,您是否有任何关于如何使其可用的提示?
    • 对于我的几乎所有项目,我只需将其保持在“范围内”即可。有几次我用过DIC。当我说 DIC 时,我并不是说将整个容器传递给所有东西并将其变成服务定位器,而是一个 DIC,它可以为我连接我的东西。
    猜你喜欢
    • 2016-04-06
    • 1970-01-01
    • 2014-11-10
    • 1970-01-01
    • 2010-11-27
    • 2021-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多