PHP中的Singleton v单实例数据库连接

时间:2014-06-10 11:39:37

标签: php oop design-patterns singleton database-connection

我正在用PHP教我自己的OOP。

我正在创建几个小型网络应用程序并且已经遵循了很多教程,这些教程要么通过Singleton创建数据库(使用PDO),要么通过传递全局。我已经读到这些几乎是同一件事,并且要像瘟疫那样被避免。

因此,我观看了Google Tech会谈清洁代码,并阅读了几乎所有关于依赖注入等的SO文章。我有几个问题。

  1. 干净的代码视频表明你不应该做的工作'在你的构造函数中。这是'工作'参考业务逻辑。 IE浏览器。如果我的班级工作是创建另一个对象,那么这是一种正常的工作'?
  2. 例如,在尝试符合单个repsonibility类时,我创建了三个。

    1. 类DB - 实际连接到数据库。
    2. 类DBFactory - 创建连接数据库的DB对象。
    3. 类DBInstance - 返回DBFactory创建的PDO对象的单个实例。
    4. 请注意,我尝试创建单个实例,而不创建Singleton模式。

      所以我尝试将每个类的依赖项传递给链。我发现自己处于一个需要创建所有对象的位置(从DB向下),因此我可以注入依赖项。出于某种原因,我认为它会以另一种方式起作用,我会创建第一个对象,这将为我创造第二个对象等。我明显错过了什么?

      希望这也有助于其他人 - 似乎有很多关于这些东西和数据库的问题,但很少有很好的例子。

      (我应该提到这确实有效,我确实从数据库中获得了酒店名称列表!)

      TestCode.php

      include './classes/DB.php';
      include './classes/DBFactory.php';
      include './classes/DBInstance.php';
      include './classes/Location.php';
      
      $db = new DB;
      $dbfactory = new DBFactory($db);
      $dbinstance = new DBInstance($dbfactory);
      $dbh = $dbinstance->getDbInstance();
      
      //Example business logic
      $location_names = Location::getLocationNames($dbh);
      print_r($location_names);
      

      Class DB.php:

      class DB {
      
          private $_dbhost = 'myhost';
          private $_dbname = 'myname';
          private $_dbuser = 'myuser';
          private $_dbpass = 'mypass';
      
          private $_error;
      
          public function connect() {
              try {
                  return new PDO("mysql:host=$this->_dbhost;dbname=$this->_dbname", 
                      $this->_dbuser, $this->_dbpass);
              }
      
              catch (PDOException $e) {
                  $this->_error = 'Error! ' . $e->getMessage() . '<br />';
                  die();
              }
          }
      
          public function getError() {
              if (isset($this->_error)) {
                  return $this->_error;
              }
          }
      }
      

      Class DBFactory.php

      class DBFactory {
      
          private $_dbh;
      
          public function __construct(DB $db) {
              $this->_dbh = $db;
          }
      
          public function Create() {
              return $this->_dbh->Connect();
          }
      }
      

      Class DBInstance.php

      class DBInstance {
      
          private static $_dbinstance;
      
          public function __construct(DBFactory $dbfactory) {
      
              if (!isset(self::$_dbinstance)) {
                  self::$_dbinstance = $dbfactory->Create();
              }
          }
      
          public function getDbInstance() {
              return self::$_dbinstance;
          }
      }
      

3 个答案:

答案 0 :(得分:1)

KISS

不要让事情变得比他们复杂,当然这只是我的意见,但正如我所看到的那样,你正在构建一个复杂的解决方案来解决其他人说可能存在的问题。 Php不是多线程的,因此有一个最大的争论是落水。 (在极少数情况下可能会这样)

我现在使用单例作为我的数据库连接大约15年了,从来没有遇到任何问题,我确实玩了不同的连接,只有一个单例处理几个连接实例,但无论如何......它很有用每个看着代码的人都直接理解它。 我没有使用全局变量,因为它们可以被覆盖并且很难预测(当它保存正确的对象时,以及何时/为什么它们没有)

使用OOP使您的代码更清晰,更易于使用且更灵活。 不要用它来修复那些不存在的问题,并使你的代码更复杂,因为其他人告诉你。

处理多个不同连接的db-connection单例类的一个非常简单的例子。

class singleton{
  private static $_instances=array();

  public static function getInstance($connectionName){
     if(!isset(self::$_instance[$connectionName]){
        self::$_instance[$connectionName]=self::_getConnection($connectionName);
     }
     return self::$_instance[$connectionName];
  }

}

只是我的2美分

答案 1 :(得分:1)

如果你有单身人士,为什么你有工厂?这是不必要的。

这是一场永无休止的辩论,但我主张不要将单例用于数据库连接。

就大多数应用程序而言,您只有一个数据通道,您可以认为您的数据库连接是唯一的,但这可能并非总是如此。

实际上,创建单例数据库连接所做的努力甚至比创建常规数据库更大。

此外,您的班级DB不可配置,因此,您需要在连接参数更改时更改它。我认为DB是一个非常糟糕的名字。

我宁愿调用此Storage并执行以下操作:

inteface Storage {
    public function insert($container, array $data);
    public function update($container, array $data, $where);
    public function delete($container, $where);
    public function getAll($container);
    public function getOne($identifier);
}

final class PdoStorage implements Storage {
    private $dbh;
    private $dsn;
    private $user;
    private $pswd;

    public function __construct($dsn, $user, $pswd) {
        $this->dsn = $dsn;
        $this->user = $user;
        $this->pswd = $pswd;
    }

    // Lazy Initialization
    private function connect() {
        if ($this->dbh === null) 
            $this->dbh = new PDO($this->dsn, $this->user, $this->pswd);
    }

    public function insert($container, array $data) {
        $this->connect();

        // ... omitted for brevity
    }
}

现在,当您需要数据库存储时,您可以:

$someObj = new SomeClass(new PdoStorage(...));

现在您可能想知道是否需要为每个依赖于它的单个对象创建PdoStorage

答案是:没有

现在您可以使用工厂来简化您的生活。

class SomeFactory {
    private $defaultStorage;

    public function __construct(Storage $storage) {
        $this->defaultStorage = $storage;
    }

    public function create($type) {
        // Somehow fetches the correct class to instantiate and put it into $class variable , for example... and then

        return new $class($this->defaultStorage); // Or you'd better do this with reflection
    }
}

$factory = new SomeFactory(new PdoStorage(...));
$factory->create('SomeClass');

这样,如果需要,您可以只使用一个或多个数据库连接器。

答案 2 :(得分:1)

您的代码似乎按照您的意愿执行..但也许我们可以使用继承来减少对象实例化,也许我们可以避免在实例化类中使用静态属性。

此外,关于使用能够处理多个连接的依赖注入模式,但支持使用它的单个实例。

之后的第一个例子
$params = array
 ('host'=>'localhost',
  'db'=>'ice',
  'user'=>'kopitar',
  'pass'=>'topnet',
  'charset'=>'utf8'); // passing the charset explicitely is great

$handle = new handle($params);
$db = $handle->getInstance();

我们可以将$db传递给我们的函数

$location_names = Location::getLocationNames($db); 

或整个$句柄。只要$ handle没有重建,它将始终返回相同的数据库连接。

$location_names = Location::getLocationNames($handle);

如果我想重建,我需要整个$handle

$handle->__construct(/* params but with another database infos */);
$db2 = $handle->getInstance();

对于课程,我认为我们希望params从instanciated类到达,所以我们可以稍后更改它们。

class db {
  function __construct($params) {
    foreach ($params as $param => $value) {
      $this->{$param} = $value; // assigns the connections infos
    }
  }
  protected function connect() {
    $dsn = 'mysql:host='.$this->host.';dbname='.$this->db.';charset='.$this->charset;
    return new PDO($dsn,$this->user,$this->pass);
  }
}

工厂从params创建一个连接并将其传递给其他东西,好工厂

class factory extends db {
  protected function create() {
    return $this->connect();
  }
}

现在我们想让我们的对象保持连接,只要我们不重建它。所以我们把它给实例

class instance extends factory {
  function instantiate() {
    $this->instance = $this->create();
  }
}

最后但并非最不重要的是,我们的句柄返回实例。它可以在实例类中.....................

但我觉得有四个人并没有找到真正的理由不这样做。

class handle extends instance {
  function __construct($params) {
    db::__construct($params);
    $this->instantiate(); // when we construct a handle, we assign an instance to the instance property
  }
  function getInstance() {
    return $this->instance;
  }
}