PHP PDO的Singleton替代品 [英] Singleton alternative for PHP PDO

查看:43
本文介绍了PHP PDO的Singleton替代品的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我用来连接到我的MySQL数据库的类. 如您所见,我正在使用Singleton Pattern,但是几乎每个帖子都说这是一个非常糟糕的模式.创建数据库连接类的最佳方法是什么?有更好的模式吗?

This is my class that I'm using to connect to my MySQL database. As you can see I'm using the Singleton Pattern but almost every post says it is a very bad pattern. What is the best approach to create a database connection class? Is there a better pattern?

class DB extends PDO {

    function __construct() {
        try {
            parent::__construct('mysql:host=' . 'localhost' . ';dbname=' . 'kida', 'root', 'root', array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'");
            parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch(PDOException $e) {
            echo $e->getMessage();
        }
    }

    public static function get_instance() {
        static $instance = false;
        if(!$instance) $instance = new self;
        return $instance; //returns pdo object.
     }
}

推荐答案

使用单例模式(或反模式)被认为是不好的做法,因为它使您的代码测试变得非常困难,并且依赖度非常复杂,直到项目变得难以管理为止在某一点.每个php进程只能有一个对象的固定实例.在为代码编写自动化的单元测试时,您需要能够将您要测试的代码替换为以可预测的方式运行的test-double.当您要测试的代码使用单例代码时,则无法将其替换为测试双数.

Using the singleton-pattern (or antipattern) is considered bad practice because it makes testing your code very hard and the depencies very convoluted until the project becomes hard to manage at some point. You can only have one fixed instance of your object per php-process. When writing automated unit-tests for your code you need to be able to replace the object the code you want to test uses with a test-double that behaves in a prdictable manner. When the code you want to test uses a singleton, then you cannot replace that with a test double.

(据我所知)组织对象(例如您的数据库对象和使用数据库的其他对象)之间的交互的最佳方法是反转视差的方向.这意味着您的代码不是从外部源(大多数情况下是全局的,例如代码中的静态"get_instance"方法)请求它所需要的对象,而是从外部获取其依赖对象(它所需要的对象)在需要它之前.通常,您会使用Depency-Injection Manager/Container,例如 symfony项目来组成您的对象.

The best way (to my knowlege) to organize the interaction between objects (like your Database-Object and other objects using the database) would be to reverse the direction of the depencies. That means that your code is not requesting the object it needs from an external source (in most cases a global one like the static 'get_instance' method from your code) but instead gets its depency-object (the one it needs) served from outside before it needs it. Normally you would use a Depency-Injection Manager/Container like this one from the symfony project to compose your objects.

使用数据库对象的对象将在构造时注入它.它可以通过setter方法或在构造函数中注入.在大多数情况下(不是全部),最好在构造函数中注入依赖关系(您的数据库对象),因为那样使用数据库对象的对象永远不会处于无效状态.

Objects that use the database-object would get it injected upon construction. It can be injected either by a setter method or in the constructor. In most cases (not all) is it better to inject the depency (your db-object) in the constructor because that way the object that uses the db-object will never be in an invalid state.

示例:

interface DatabaseInterface
{
    function query($statement, array $parameters = array());
}

interface UserLoaderInterface
{
    public function loadUser($userId);
}

class DB extends PDO implements DatabaseInterface
{
    function __construct(
        $dsn = 'mysql:host=localhost;dbname=kida',
        $username = 'root',
        $password = 'root',
    ) {
        try {
            parent::__construct($dsn, $username, $password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'");
            parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch(PDOException $e) {
            echo $e->getMessage();
        }
    }

    function query($statement, array $parameters = array())
    {
        # ...
    }
}

class SomeFileBasedDB implements DatabaseInterface
{
    function __construct($filepath)
    {
        # ...
    }

    function query($statement, array $parameters = array())
    {
        # ...
    }
}

class UserLoader implements UserLoaderInterface
{
    protected $db;

    public function __construct(DatabaseInterface $db)
    {
        $this->db = $db;
    }

    public function loadUser($userId)
    {
        $row = $this->db->query("SELECT name, email FROM users WHERE id=?", [$userId]);

        $user = new User();
        $user->setName($row[0]);
        $user->setEmail($row[1]);

        return $user;
    }
}

# the following would be replaced by whatever DI software you use,
# but a simple array can show the concept.


# load this from a config file
$parameters = array();
$parameters['dsn'] = "mysql:host=my_db_server.com;dbname=kida_production";
$parameters['db_user'] = "mydbuser";
$parameters['db_pass'] = "mydbpassword";
$parameters['file_db_path'] = "/some/path/to/file.db";


# this will be set up in a seperate file to define how the objects are composed
# (in symfony, these are called 'services' and this would be defined in a 'services.xml' file)
$container = array();
$container['db'] = new DB($parameters['dsn'], $parameters['db_user'], $parameters['db_pass']);
$container['fileDb'] = new SomeFileBasedDB($parameters['file_db_path']);

# the same class (UserLoader) can now load it's users from different sources without having to know about it.
$container['userLoader'] = new UserLoader($container['db']);
# or: $container['userLoader'] = new UserLoader($container['fileDb']);

# you can easily change the behaviour of your objects by wrapping them into proxy objects.
# (In symfony this is called 'decorator-pattern')
$container['userLoader'] = new SomeUserLoaderProxy($container['userLoader'], $container['db']);

# here you can choose which user-loader is used by the user-controller
$container['userController'] = new UserController($container['fileUserLoader'], $container['viewRenderer']);

请注意不同的类之间如何彼此不了解.它们之间没有直接的依赖关系.这是通过不需要构造函数中的实际类来完成的,而是需要提供所需方法的接口.

Notice how the different classes no not know about each other. There are no direct depencies between them. This is done by not require the actual class in the constructor, but instead require the interface that provides the methods it needs.

这样,您就可以始终为类编写替换内容,而只需在依赖注入容器中替换它们即可.您不必检查整个代码库,因为替换只需实现所有其他类使用的相同接口.您知道一切都会继续进行,因为使用旧类的每个组件都只知道该接口,并且仅调用该接口已知的方法.

That way you can always write replacements for your classes and just replace them in the depency-injection container. You do not have to check the whole codebase because the replacement just has to implement the same interface that is used by all other classes. You know that everything will continue to work because every component using the old class only knows about the interface and calls only methods known by the interface.

P.S .:请原谅我对symfony项目的不断引用,这正是我最习惯的方法.其他项目(例如Drupal,Propel或Zend)也可能具有这样的概念.

P.S.: Please excuse my constant references to the symfony project, it is just what i am most used to. Other project's like Drupal, Propel or Zend probably also have concepts like this.

这篇关于PHP PDO的Singleton替代品的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆