Singleton v Connecting to a single DB instance in PHP

I am moving on to learning OOP in PHP.

I create a couple of small web applications and follow a lot of tutorials that either create a database (using PDO) through Singleton, or through global distribution. I read that this is almost the same and should be avoided like the plague.

So, I watched Tech Tech Talks clean code and read almost every SO article on dependency injection and the like. I have a few questions.

  • Clean videos show that you should not "work" with your designers. This is a β€œjob” in relation to business logic. I.e. If my class task is to create another object, is it something like a β€œjob”?

For example, when I tried to reconcile with individual rendering classes, I created three.

  • DB of the class that actually connects to the database.
  • DBFactory class - which creates a DB object that connects to the database.
  • DBInstance class - which returns a single instance of the created DBFactory PDO.

Note that I'm trying to create a single instance without creating a Singleton template.

Therefore, I am trying to pass my dependencies for each class in the chain. I am in a position where I need to create all objects (from DB down) so that I can embed dependencies. For some reason, I thought it would work differently, I would create the first object, which would create a second for me, etc. Am I explicitly missing something?

Hope this helps others too - there seem to be a lot of questions related to this material and databases, but very few good examples.

(I have to mention that it really works, I get a list of hotel names from the database!)

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); 

DB.php Class:

 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
source share
3 answers

Your code seems to do what you want, but maybe we can use fewer objects using inheritance, and maybe we can avoid static properties in non-deterministic classes.

Also regarding the use of a dependency injection pattern that is capable of handling multiple compounds but supports the use of one instance of it. first of all, classes after

 $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(); 

we can either pass $db to our functions

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

or all $ handle. until $ handle is restored, it will always return the same database connection.

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

if i want to restore i need all $handle

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

Regarding classes, I think we want params to come from an instanciated class, so we can change them later.

 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); } } 

factory creates a connection from the parameters and passes it to something else, a good factory

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

now we want our object to keep in touch until we rebuild it. therefore we give an instance of it

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

and, just as importantly, our handle that returns the instance. he may be in a classroom class ...

but I feel four and I find no real reason not to do this.

 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; } } 
+1
source

KISS

Do not make things more complicated than they are, of course, this is only my opinion, but, as I see it, you are building a complex solution to a problem that someone else can say in some cases. Php does not have multithreading, so one of the biggest arguments goes overboard. (in very rare cases it can be)

I have been using singleletons for my database connections for 15 years and have never experienced problems with them, I deal with various connections when one singleton processes several connection instances, but whatever it is ... it works fine and everyone who looks at the code, understands this directly. I do not use global variables because they can be overwritten and difficult to predict (when it contains the correct object and when / why it does not)

Use OOP to make your code cleaner, easier to use, and more flexible. Do not use it to fix problems that aren't there and make the code more complicated because others tell you.

A very simple example of a singleton db connection class that handles several different connections.

 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]; } 

}

only my 2 cents

+1
source

Why do you have a factory if you have a singleton? It's useless.

This is an endless discussion, but I advocate not using singletones for database connections.

As in most applications, you have only one data channel, you can read a unique database connection, but this may not always be true.

In fact, the efforts made to create a one-point database connection are even more than just creating a regular one.

In addition, your DB class is not configurable, so you need to change it when your connection settings change. And I think DB is a very bad name for this.

I would call it Storage and do something like:

 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 } } 

Now that you need the database storage, follow these steps:

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

Now it may seem to you that you need to create PdoStorage for each individual object that depends on it.

The answer is no !

Now you can use the factory to simplify your life.

 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'); 

This way you can have only one database connector or more if you need to.

+1
source