24

I don't want to create a discussion about singleton better than static or better than global, etc. I read dozens of questions about similar subjects on SO, but I couldn't come up with an answer to this SPECIFIC question, so I hope someone could now illuminate me by answering this question with one (or more) real simple EXAMPLES, and not just theoretical discussions.

In my app I have the typical DB class to abstract the DB layer and to perform tasks on DB without having to write everywhere in code mysql_connect / mysql_select_db / mysql...

I could write the class either as a STATIC CLASS:

class DB
{
   private static $connection = FALSE; //connection to be opened

   //DB connection values
   private static $server = NULL; private static $usr = NULL; private static $psw = NULL; private static $name = NULL;

   public static function init($db_server, $db_usr, $db_psw, $db_name)
   {
      //simply stores connections values, without opening connection
   }

   public static function query($query_string)
   {
      //performs query over alerady opened connection, if not open, it opens connection 1st
   }

   ...
}

OR as a SINGLETON:

class DBSingleton
{
   private $inst = NULL;
   private $connection = FALSE; //connection to be opened

   //DB connection values
   private $server = NULL; private $usr = NULL; private $psw = NULL; private $name = NULL;

   public static function getInstance($db_server, $db_usr, $db_psw, $db_name)
   {
      //simply stores connections values, without opening connection

      if($inst === NULL)
         $this->inst = new DBSingleton();
      return $this->inst;
   }
   private __construct()...

   public function query($query_string)
   {
      //performs query over already opened connection, if connection is not open, it opens connection 1st
   }

   ...
}

Then after in my app if I want to query the DB i could do

//Performing query using static DB object
DB:init(HOST, USR, PSW, DB_NAME);
DB::query("SELECT...");

//Performing query using DB singleton
$temp = DBSingleton::getInstance(HOST, USR, PSW, DB_NAME);
$temp->query("SELECT...");

To me Singleton has got the only advantage to avoid declaring as static each method of the class. I'm sure some of you could give me an EXAMPLE of real advantage of singleton in this specific case. Thanks in advance.

Marco Demaio
  • 33,578
  • 33
  • 128
  • 159
  • 1
    Whoever voted down could please leave a comment and explain. – Marco Demaio May 19 '10 at 16:13
  • 2
    There is absolutely no necessity to make a database class a singleton or behave as a static class. I mean what purpose does it serve? If you are looking to make an application that simultaneously needs two different database connections, you're basically screwed already. And lazy initialization (of the actual connection) is perfectly possible with a class that allows multiple instances. – Decent Dabbler Apr 09 '11 at 15:22
  • @fireeyedboy: thanks for you comment but I don't exactly understand your point. At the end do you think it's better a singleton or static class and why? – Marco Demaio Apr 09 '11 at 15:27
  • none of the above. :) I strongly advice against any of those two options. Why do you feel one of those two options are necessary? – Decent Dabbler Apr 09 '11 at 15:30
  • please see my answer for a simplified explanation of how you could implement lazy loading of the connection and still be flexible. – Decent Dabbler Apr 09 '11 at 15:42
  • @fireeyedboy You answer is - compared to the 2 given possibilities by Marco Demaio - the worst one. Sorry. – Sliq Aug 14 '12 at 18:40

5 Answers5

7

What is wrong with the following (simplified) example:

class Database
{
    protected $_connection;

    protected $_config;

    public function __construct( array $config ) // or other means of passing config vars
    {
        $this->_config = $config;
    }

    public function query( $query )
    {
        // use lazy loading getter
        return $this->_getConnection()->query( $query );
    }

    protected function _getConnection()
    {
        // lazy load connection
        if( $this->_connection === null )
        {
            $dsn = /* create valid dsn string from $this->_config */;

            try
            {
                $this->_connection = new PDO( $dsn, $this->_config[ 'username' ], $this->_config[ 'password' ] );
            }
            catch( PDOException $e )
            {
                /* handle failed connecting */
            }
        }

        return $this->_connection;
    }
}

$db1 = new Database( array(
    'driver'   => 'mysql',
    'host'     => 'localhost',
    'dbname'   => 'test',
    'username' => 'test_root',
    'password' => '**********'
) );

$db2 = new Database( array(
    'driver'   => 'pgsql',
    'host'     => '213.222.1.43',
    'dbname'   => 'otherdb',
    'username' => 'otherdb_root',
    'password' => '**********'
) );

$someModel       = new SomeModel( $db1 );
$someOtherModel  = new SomeOtherModel( $db2 );
$yetAnotherModel = new YetAnotherModel( $db2 );

This demonstrates how you can make use of lazy loading connections, and still have flexibility to use different database connections.

The database instances will only connect to their individual connection when an object that consumes one of the instances (in this case one of the models) decides to call a method of the instance.

Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
  • Sorry, but I don't undertsand your example. 1) it's not a Singleton, but a genric class, so it's obvious that you can create multiple istances to differnet DBs that's what a generic class is meant to be, on contrary a SIngleon is meant to return you always the same object with the same connection. 2) when you say " this example", even the static class and the Singleton only connetcs when needed, the init fucntion simply strores the DB connection parameters, the connection is performed only if needed duirng the call to the query function (read comments in my question code). – Marco Demaio Aug 13 '11 at 10:25
  • 1
    Marco, the reason I advise against using a singleton is because, when your application (or a future application) needs two different database connections at the same time, you can't use that class anymore, because it can only accommodate for one connection. Furthermore I presume you use this singleton to easily get the instance from anywhere you like, right? But that creates strong coupling. However, when you pass the database connection to the constructor of the consuming object, as in my example, you create loose coupling, so you can easily swap it for another class with the same interface. – Decent Dabbler Aug 13 '11 at 16:33
  • oh I didn't understand you were actually suggesting not to use a Singleton or static class at all, but to use a normal class. Now I understood your point. – Marco Demaio Aug 16 '11 at 17:09
  • @Marco: no problem. Hope it's useful information, as well as a useful example for you. I'd strongly advice you to this route anyway. – Decent Dabbler Aug 16 '11 at 17:37
  • @fireeyedboy Haha nice one, I'm doing it like you wrote, but people told me (collegues) that it's bad to pass the db var every time you create a new object. Then I searched for an alternative and ended up on a singleton class. Then I read singleton is bad, and now I just ended up again here, with your answer as the accepted and most upvoted... damn, coding is hard... – Sliq Aug 14 '12 at 17:48
  • 1
    @Panique: did you ask your collegues why they say it's bad to pass the `$db` var every time? Moreover I presume you did not understand fireeyedboy answer, he's not suggesting to pass `$db` var every time, he is suggesting to create the DB class as a normal class, (no singleteon/static), in this way you can create multiple instances of the db class to connect to multiple DB. – Marco Demaio Aug 17 '12 at 15:41
4

In my most recent project, I actually went against the "good" design principles by making the database class entirely static. The reason behind this is that I used a lot of caching on PHP objects. Originally I had the database passed in through the constructor of each object as a dependency injection, however I wanted to make sure that the database didn't have to connect unless absolutely necessary. Thus, using a database as a member variable of that object would not have been practical because if you unserialized an object from the cache, you wouldn't want to connect to the database unless you actually performed an operation on it.

So in the end I had only two (public) static functions, Database::fetch() and Database::execute() which would check whether or not it had already connected, and if not, it would connect and perform the query. This way I wouldn't have to worry about deserialization and would connect as seldom as possible. It technically makes unit testing impossible though.

You don't always have to follow every single good practice. But I would still recommend against doing what I did since some would consider it premature optimization.

Lotus Notes
  • 6,302
  • 7
  • 32
  • 47
4

My advice: STOP using Singleton and static all together.

Why? Because you will insert dependencies that will render your code unusable in other projects, and will not allow to unit test it. Also forget about loose coupling if using singleton.

The alternatives? Dependency Injection. http://www.potstuck.com/2009/01/08/php-dependency-injection

johnlemon
  • 20,761
  • 42
  • 119
  • 178
  • PHP avoid static classes to avoid dependencies, but then I need to use global everywhere: http://stackoverflow.com/questions/10486107/php-do-not-use-static-classes-to-avoid-dependencies-but-then-i-need-to-use-glob – Marco Demaio May 07 '12 at 17:10
  • Side-fact: It's nearly 2015 now and even the biggest frameworks and basically all ORM libs use static methods. – Sliq Nov 27 '14 at 23:18
1

Making DB library static is certainly shorter and quicker, than doing:

$db = DBSingleton::blabla(); // everytime I need ya

But also, since it is global, tempting to use everywhere.

So, choose other methods if you want clean code... and choose static if you need quick code ;-)

Martin
  • 29
  • 1
-1
/* Data base*/
 class Database
{
    /* Database field definition */
    private static $_instance; /instance
    private $_connection;
    private $DB_USER = "database_user_name_here";
    private $DB_PASS = "your_password_here";
    private $DB_NAME = "your_database_name_here";
    private $DB_SERVER = "localhost";

    /* Initiate the database connection */
    private function __construct()
    {
        $this->_connection = new mysqli($this->DB_SERVER ,
                                        $this->DB_USER ,
                                        $this->DB_PASS ,
                                        $this->DB_NAME);
        /* Test if connection succeeded */
        if (mysqli_connect_errno()) {
            die("Database connection failed: " .
                mysqli_connect_error() .
                " (" . mysqli_connect_errno() . ")"
            );
        }
    }

    /**
     * Instance of the database
     * @return Database
     *
     */
    public static function Instance()
    {
        if (!self::$_instance) { // If no instance then make one
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    /**
     * Void duplicate connection
     */
    private function __clone() { }

    /* Return a connection */
    public function getConnection()
    {
        return $this->_connection;
    }

}

/** This is how you would use it in a different class.
  @var TYPE_NAME $connection */
$db = Database::Instance();
$connection = $db->getConnection();
Magellan
  • 1,224
  • 9
  • 16