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

Posted

技术标签:

【中文标题】PHP pdo 实例作为私有静态属性【英文标题】:PHP pdo instance as private static property 【发布时间】: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 服务容器的“限制” 【参考方案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,它可以为我连接我的东西。

以上是关于PHP pdo 实例作为私有静态属性的主要内容,如果未能解决你的问题,请参考以下文章

Python:类属性,实例属性,私有属性与静态方法,类方法,实例方法

PHP单例模式 要点

Python面向对象之封装

面向对象的成员: 实例变量 类变量 实例方法 类方法 静态方法 属性 私有

第174天:面向对象——公有属性私有属性和静态属性

javascript 实例 静态 公共 私有