在 PHP 中,具有数据库访问权限的单例是否有用例?

2022-08-30 06:54:21

我通过PDO访问我的MySQL数据库。我正在设置对数据库的访问,我的第一次尝试是使用以下方法:

我首先想到的是:global

$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'root', 'pwd');

function some_function() {
    global $db;
    $db->query('...');
}

这被认为是一种不好的做法。经过一番搜索,我最终得到了辛格尔顿模式,它

“适用于需要一个类的单个实例的情况。

根据手册中的示例,我们应该这样做:

class Database {
    private static $instance, $db;

    private function __construct(){}

    static function singleton() {
        if(!isset(self::$instance))
            self::$instance = new __CLASS__;

        return self:$instance;
    }

    function get() {
        if(!isset(self::$db))
            self::$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'user', 'pwd')

        return self::$db;
    }
}

function some_function() {
    $db = Database::singleton();
    $db->get()->query('...');
}

some_function();

当我可以做到这一点时,为什么我需要那个相对较大的类?

class Database {
    private static $db;

    private function __construct(){}

    static function get() {
        if(!isset(self::$db))
            self::$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'user', 'pwd');

        return self::$db;
    }
}

function some_function() {
    Database::get()->query('...');
}

some_function();

最后一个工作完美,我不需要再担心了。$db

如何创建较小的单例类,或者是否有我在PHP中缺少的单例用例?


答案 1

单例在 PHP 中很少使用 (如果不是说不的话)。

在对象位于共享内存中的语言中,单例可用于保持较低的内存使用率。无需创建两个对象,而是从全局共享的应用程序内存中引用现有实例。在 PHP 中没有这样的应用程序内存。在一个请求中创建的单例恰好为该请求存活。在同一时间完成的另一个请求中创建的单例仍然是一个完全不同的实例。因此,单例的两个主要目的之一在这里不适用。

此外,许多在概念上只能在应用程序中存在一次的对象不一定需要语言机制来强制执行此操作。如果只需要一个实例,则不要实例化另一个实例。只有当你可能没有其他实例时,例如,当你创建第二个实例时小猫死亡时,你才可能有一个有效的单例用例。

另一个目的是在同一请求中为实例提供一个全局访问点。虽然这听起来很可取,但实际上并非如此,因为它会创建与全局范围的耦合(就像任何全局变量和静态值一样)。这使得单元测试更加困难,并且您的应用程序通常更难维护。有一些方法可以缓解这种情况,但通常,如果您需要在许多类中具有相同的实例,请使用依赖关系注入

请参阅我的 PHP 单例幻灯片 - 为什么它们很糟糕,以及如何从应用程序中消除它们以获取更多信息。

就连辛格尔顿模式的发明者之一埃里希·伽马(Erich Gamma)现在也对这种模式表示怀疑:

“我赞成放弃辛格尔顿。它的使用几乎总是一种设计气味”

进一步阅读

如果在上述之后,您仍然需要帮助来决定:

Singleton Decision Diagram


答案 2

好吧,当我第一次开始我的职业生涯时,我想知道了一段时间。以不同的方式实现它,并提出了两个选择不使用静态类的理由,但它们是相当大的原因。

一个是你会发现,很多时候,你绝对确定你永远不会有多个实例,你最终会有第二个。你最终可能会得到第二台显示器,第二个数据库,第二台服务器 - 无论什么。

发生这种情况时,如果您使用了静态类,那么重构的情况要比使用单例的情况糟糕得多。单例本身就是一种不稳定的模式,但它很容易转换为智能工厂模式 - 甚至可以转换为使用依赖注入,而不会有太多麻烦。例如,如果你的单例是通过getInstance()获得的,你可以很容易地将其更改为getInstance(databaseName),并允许多个数据库 - 没有其他代码更改。

第二个问题是测试(老实说,这与第一个问题相同)。有时,您希望将数据库替换为模拟数据库。实际上,这是数据库对象的第二个实例。这与单例相比,静态类更难处理,您只需要模拟getInstance()方法,而不是静态类中的每个方法(在某些语言中可能非常困难)。

这真的归结为习惯 - 当人们说“全球”不好时,他们有很好的理由这么说,但在你自己遇到问题之前,它可能并不总是很明显。

你能做的最好的事情就是问(就像你一样),然后做出选择,观察你的决定的后果。拥有解释代码随时间演变的知识比一开始就做对要重要得多。


推荐