【问题标题】:PHP - multiple different databases dependency injected classPHP - 多个不同的数据库依赖注入类
【发布时间】:2011-12-19 23:36:35
【问题描述】:

过去几个小时我一直在尝试寻找“最佳”、最合乎逻辑等方式的答案,以编写一个 php 数据库类以同时连接到一个 postgresql 数据库和一个 mysql 数据库。另外,我想采用依赖注入设计,但对整个概念还是陌生的。

到目前为止,我已经想出了......

class Database {

    public function PgSqlConnect() {
            /* Connect to database */
        $host = 'localhost';
        $dbname = '---';
        $user = '---';
        $pass = '---';
        $timeout = 5;   /* seconds */

        try {
            $pgsql_dbh = new PDO("pgsql:host=$host; dbname=$dbname", $user, $pass); 
            $pgsql_dbh->setAttribute( PDO::ATTR_TIMEOUT, $timeout ); 
            $pgsql_dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

            return $pgsql_dbh;
        } catch( PDOException $e ) {
            echo 'Unable to connect to database: ' . $e->getMessage();
        }
    }


    public function MySqlConnect() {
            /* Connect to database */
        $host = 'localhost';
        $dbname = '---';
        $user = '---';
        $pass = '---';
        $timeout = 5;   /* seconds */

        try {
            $mysql_dbh = new PDO("mysql:host=$host; dbname=$dbname", $user, $pass); 
            $mysql_dbh->setAttribute( PDO::ATTR_TIMEOUT, $timeout ); 
            $mysql_dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

            return $mysql_dbh;
        } catch( PDOException $e ) {
            echo 'Unable to connect to database: ' . $e->getMessage();
        }
    }

}

显然,重复的代码违反了 DRY 方法。我知道并见过很多多数据库连接的例子,但大多数都处理相同的驱动程序并且不提供 DI 功能。

我还应该补充一点,我已经考虑将连接详细信息放入数据库类构造函数中...

$driver = 'mysql';
...
$mysqldb = new Database($driver,$un,$pw,...);

$driver = 'pgsql';
...
$pgsqldb = new Database($driver,$un,$pw,...);

但我不知道这是否真的是一个好主意,也不知道它与 DI 的效果如何。

非常感谢!

【问题讨论】:

    标签: php database oop dependency-injection


    【解决方案1】:

    您应该首先为所有数据库操作创建一个接口。

    interface IDatabase
    {
        function connect();
        function query();
        ...
    }
    

    然后有不同的驱动类实现这个接口

    class MySQLDB implements IDatabase
    {
    }
    class PGSQLDB implements IDatabase
    {
    }
    

    这样你就可以轻松使用依赖注入了。

    class Test
    {
       private $db;
    
       function __construct(IDatabase $db)
       {
            $this->db = $db;
       }
    }
    

    你可以这样称呼它:

    $mysqldb = new MySQLDB();
    $test = new Test($mysqldb);
    or
    $pgsqldb = new PGSQLDB();
    $test = new Test($pgsqldb);
    

    【讨论】:

    • 谢谢 Vikk,您能否澄清一下如果我将 PDO 连接代码放在 IDatabase::connect 中,那么它将如何辨别要使用哪个 PDO 驱动程序?
    • 接口不会(也不能)有任何代码,除了函数和它们的签名。实现这些功能的是具体的类(即 MySqlDB)。所以在 MySQLDB 的 connect() 函数中,你将为 MySQL 编写连接代码,在 PGSQLDB 的 connect() 函数中,你将为 PGSQL 编写连接代码
    • 我现在明白了。所以我只是在每个具体类中都有重复的代码?
    • 为避免重复代码,您可以扩展一个抽象类。或者等待 php 5.4 并使用特征。
    • @Vikk 你能告诉我为什么你有这个界面吗?这样你就可以通过任何类而不必提前知道类,只要它实现了 IDatabase?没有界面也能做到吗?
    【解决方案2】:

    为避免重复代码,您可以扩展一个抽象类

    abstract class AbstractDb {
        public function connect() {
            // common code to avoid duplication
            echo 'connected!';
        }
    
        abstract public function escapeField();
        abstract public function escapeValue();
    }
    
    class MySQL extends AbstractDb {
        public function escapeField() {
            // Db-specific method
        }
    
        public function escapeValue() {
            // Db-specific method
        }
    }
    
    $db = new MySQL;
    $db->connect();
    

    或者使用组合,让 Db 类为特定于 db 的方法使用不同的驱动程序

    interface IDriver {
        public function escapeField();
        public function escapeValue();
    }
    
    class MySQLDriver implements IDriver {
        public function escapeField() {
            // Db-specific method
        }
    
        public function escapeValue() {
            // Db-specific method
        }
    }
    
    class Db {
        public function __construct($driver) {
            $this->driver = $driver;
        }
    
        public function connect() {
            // common code here ? idk, it's just an example
            echo 'connect!';
        }
    
        // this method is db-specific, so we call the driver
        public function escapeField($field) {
            return $this->driver->escapeField($field);
        }
    
        public function escapeValue() {
            // same here
        }
    }
    
    $db = new Db(new MySQLDriver);
    $db->connect();
    

    在php 5.4中会有traits,所以会有更多的方法来避免代码重复。

    【讨论】:

    • 我想我理解你的方法,但我不想写抽象,因为我正在使用 PDO。当唯一更改的代码是驱动程序字符串时,我只想封装 db 代码而不为每个 db 连接复制整个方法。我在我的问题末尾附加了一个我目前正在考虑的例子。您是否建议通过构造函数将 db 类型传递给类?你还会抽象类还是使用接口?
    • 首先,PDO 带来的抽象很少,它不是 DBAL。无论如何,如果您将连接选项放在构造函数中,您就是对它们进行硬编码(不可取)。如果您改用驱动程序,将来您可以在不更改数据库类的情况下添加更多驱动程序。这种方法更加解耦、可维护和可测试。但是,如果您只需要快速修复,您的解决方案就可以解决您的问题,即使从软件设计的一般角度来看,它不是一个好的解决方案。
    • 另一种选择是使用已经存在的东西,比如Doctrine 2 DBAL。我之前没有提到它,因为我假设您正在尝试学习和构建自己的实现。
    • 对不起,我并不是要暗示 PDO 提供抽象。只有那个 PDO 才能满足我的需要,而无需添加完整的抽象。
    • 你的假设是正确的,我正在努力学习和建立自己的。非常感谢您花时间帮助我理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-02
    • 2020-10-06
    • 2012-01-03
    • 1970-01-01
    • 2021-09-11
    相关资源
    最近更新 更多