Design Patterns – Singleton [Series][How-To]

by Nikolaos Dimopoulos on January 22, 2010

in Design Patterns, How-To, MySQL, PHP, Programming, Series, Zend Framework

A note about these series. It appears that Giorgio Sironi and I had the same idea regarding Design Patterns and blogging about them. He covers the Singleton design pattern thoroughly in his blog post, which is recommended reading.

The Problem

When I started programming in PHP I was faced with creating a simple database driven web page for a Ferrari Fans Fun club. The page had 5 different sections that each accessed the database to retrieve data. Each section was included in more than one page and only the menu, header and footer were common in all pages.

My first design and implementation was horrible. I still have the files and for the purposes of this blog post I went back and checked on them and I can safely say I am ashamed of that code. But then again we all start from somewhere so that was my start and more importantly I do not program like that any more. The picture below shows how the page was constructed:

Header
Menu Content Additional information
Footer

Each of the sections was a different php script (header.php, menu.php, content.php, footer.php, info.php) and in order to retrieve information from the database for each section I had the following snippet at the top of each script:

$dbName = 'FFFF';
$dbUser = 'ffff_user';
$dbPass = 'mypassword';
$dbHost = 'localhost';

$conn = mysql_connect($dbHost, $dbUser, $dbPass);
if (!$conn) {
    die(('Cannot connect to the database :' . mysql_error());
}

$db = mysql_select_db($dbName, $conn);
if (!$db) {
    die ('Cannot select the database : ' . mysql_error());
}

Some might comment on my error handling or the naming of the variables. That is not the problem. The problem is that the snippet of code above was used in every script file (all 5 of them). As a result every page load was hitting the database 5 times. Although the intended user base was no more than 100 people, due to this design flaw I had the equivalent of 500 users.

The first step – Primitive refactoring

You might argue that the two files (header, menu) can easily be combined into one (and the same with additional information and footer) and that will save me 3 connections. The layout does not change but now each of the shaded areas represent one script:

Header
Menu Content Additional information
Footer

Although this is a good start it is not the solution to the problem. I have effectively reduced the number of hits to 3 per visitor (300 vs 500 before). The goal is to have one connection per visitor.

One step further – A global variable

I need to create the database connection, store it in a global variable, and then let the rest of the scripts access that variable – and subsequently the database connection – when needed. The pseudo code is as follows:

  1. Load script header.php
  2. Get the database credentials
  3. Create a database connection
  4. Select the database
  5. Display the data
  6. Load script content.php
  7. Get the database connection
  8. Display the data
  9. Load script footer.php
  10. Get the database connection
  11. Display the data

I need to ensure that my database connection is initiated at the beginning of every page. The script header.php is the most obvious place:

$dbName = 'FFFF';
$dbUser = 'ffff_user';
$dbPass = 'mypassword';
$dbHost = 'localhost';

$DBconn = mysql_connect($dbHost, $dbUser, $dbPass);
if (!$DBconn) {
    die(('Cannot connect to the database :' . mysql_error());
}

$db = mysql_select_db($dbName, $DBconn);
if (!$db) {
    die ('Cannot select the database : ' . mysql_error());
}

In every script thereafter I need to reference the global variable and I can then use it in that script:

global $DBconn;

Although this is an “acceptable” way of programming, maintaining all the global variables can easily be a nightmare for

  • maintenance
  • testing
  • quality control
  • any part of the code in any script that references this global variable can effectively change that variable
  • that the code will look “ugly” (hey I am proud of the code that I write :) )

Design Patterns – Singleton

A better approach to solve this problem is to use a design pattern. In this case I will use the Singleton Pattern.

The Singleton pattern is applied to a class which when called will create a database connection if the connection does not exist or pass the connection back to the caller if it has already been instantiated. This way I really do not care where the database credentials will be added and when the connection will be instantiated. The first time that I am calling the class that implements the Singleton pattern will connect to the database and have the connection stored ready to be used. The pseudo code is as follows:

  1. Load script header.php
  2. Get the database credentials
  3. Create a database connection
  4. Select the database
  5. Display the data
  6. Load script content.php
  7. Get the database connection
  8. Display the data
  9. Load script footer.php
  10. Get the database connection
  11. Display the data

The class that I created is as follows:

<?php
class Db
{
    private static $_db   = null;
    private static $_conn = null;

    private function __construct()
    {
        // This is where we have the connection parameters
        include_once 'connection.inc.php';

        $this->_conn = mysql_connect($host, $user, $password);
        if (!$this->_conn) {
            throw new Exception('Cannot connect to the database :' . mysql_error());
        }

        $db = mysql_select_db($database, $this->_conn);
        if (!$db) {
            throw new Exception('Cannot select the database : ' . mysql_error());
        }
    }

    private function __destruct()
    {
        mysql_close($this->_conn);
    }

    // The singleton method
    public static function getInstance()
    {
        if (null === self::$db) {
            self::$_db = new Db($options);
        }

        return self::$_db;
    }

    public function query($sql)
    {
        $_result = mysql_query($sql);

        if (!$_result) {
            throw new Exception('Error in query : ' . mysql_error() . "\n" . $sql);
        }

        $data = array();

        while ($row = mysql_fetch_assoc($_result)) {
            $data[$row['id']] = $row;
        }

        mysql_free_result($_result);

        return $data;
    }
}
?>

With this class available I really do not care if my database connection code is at the beginning of my scripts or not. Using this class allows me to create the database connection (if it is not established) and persist/reuse it further down the script execution.

The code for my header and menu (see graphic above) becomes:

    $sql = 'SELECT menu_id, menu_name FROM tbl_menu';
    $menu = Db::getInstance()->query($sql);

while the one for the rest of the site is exactly identical sans the query to be executed. The problem is solved (I now have one connection per visitor) and the code seems a lot tidier.

Note that the connection parameters are in a separate file which is accessed during the __construct() method of the class. You can use anything you want to supply these parameters in your class.

Conclusion

The Singleton Design Pattern is a blessing in disguise. If the ground work has not been done (i.e. create tests for your code and thoroughly document it) then it is difficult for a new developer coming into a project to understand what is going on, especially when the new developer needs to make alterations and run newly created tests.

A word of caution: If you choose to use this pattern in your application, make sure that everything you do is thoroughly documented and tested. This will make your life a lot easier in the long run and will aid in maintenance.

Update: Thanks to Jani Hartikainen for pointing out an error in the code.

Share and Enjoy:
  • Print
  • Digg
  • LinkedIn
  • del.icio.us
  • Sphinn
  • Facebook
  • Mixx
  • Google Bookmarks
  • Slashdot
  • Technorati
  • StumbleUpon
  • Twitter
  • Blogplay
  • DZone
  • SphereIt

Related Posts

{ 7 trackbacks }

Design Patterns – Singleton [Series][How-To] | Neorack Tutorials
January 22, 2010 at 14:54
Design Patterns – Singleton [Series][How-To] | Linux Affinity
January 22, 2010 at 16:25
Design Patterns – Singleton [Series][How-To] | Coder Online
January 22, 2010 at 16:44
Design Patterns – Singleton [Series][How-To] | Drakz Free Online Service
January 22, 2010 at 16:50
Design Patterns – Singleton [Series][How-To] | Drakz Free Online Service
January 22, 2010 at 18:40
Design Patterns – Singleton [Series][How-To] | Drakz Free Online Service
January 22, 2010 at 23:32
Tweets that mention Design Patterns – Singleton [Series][How-To] -- Topsy.com
January 23, 2010 at 04:12

{ 5 comments… read them below or add one }

1 Jani Hartikainen January 22, 2010 at 13:46

In my opinion singleton is often basically a glorified global variable. However, it is a step in the right direction, as it is somewhat safer and can be replaced more easily.

What confuses me of your examples is where does the constructor get the parameters? That isn’t shown.

Like or Dislike: Thumb up 0 Thumb down 0

2 Nikolaos Dimopoulos January 22, 2010 at 14:10

Jani,

Thanks for pointing that out. I have altered a bit the code to reflect the variable passing. In this example I use an include_once in the constructor since I do not want to supply the connection credentials every time I call the getInstance().

Thank you for pointing that out.

Like or Dislike: Thumb up 0 Thumb down 0

3 Jani Hartikainen January 23, 2010 at 14:37

Here’s an idea: Instead of using an include, how about using “lazy connect”?

Kinda like this.. (lets hope this shows up ok)

Db::connect(‘user’, ‘pass’, ‘db’, ‘host’);

class Db {
  public static connect($user, $pass, $db, $host) {
    $this->_user = $user;
    //etc.
  }

  public static getInstance() {
    //if no connection, use vars set in connect to open new
  }
}

You would then initialize the database connection using the connect method somewhere in your code. Assuming you’re including a file or routing all calls through index.php, it could be run there and the rest of the code does not need to care about it.

Like or Dislike: Thumb up 0 Thumb down 0

4 Jani Hartikainen January 23, 2010 at 14:38

Yeah looks like the code I just wrote in the comment won’t actually work… public static *function*, self:: instead of $this… Haven’t written PHP code in a while :D

Like or Dislike: Thumb up 0 Thumb down 0

5 Nikolaos Dimopoulos January 23, 2010 at 15:00

Yes that would work. Another way of doing this is to use an init() function which would accept all these parameters, connect to the database server and select the database. It is all up to the developer on how the class should behave.

For instance on the above I would do:

Db::init($host,$user,$password,$database);
Db::getInstance()->connect();
Db::getInstance()->selectDb();
$sql = ‘SELECT * FROM users’;
Db::getInstance()->query($sql);

This splits the implementation in different methods and you can do the lazy connect with a lot of flexibility. Instead of connecting to the server and selecting the database you can now reuse the connection to the same database server but different database. All you have to do is call

Db::getInstance()->selectDb($database);

The additional code that I am thinking is below:

private static $_host = ”;
private static $_user = ”;
private static $_pass = ”;
private static $_database = ”;

public static function init($host, $user, $password, $database)
{
$self::_host = $host;
$self::_user = $user;
$self::_pass = $password;
$self::_database = $database;
}

public function connect()
{
$this->_conn = mysql_connect($this->_host, $this->_user, $this->_pass);
if (!$this->_conn) {
throw new Exception(‘Cannot connect to the database :’ . mysql_error());
}
}

public function selectDb($database = ”)
{
if (!$this->_conn) {
throw new Exception(‘No database connection. Please connect to a server first.’);
}

$this->_database = ($database) ? $database : $this->_database;

$db = mysql_select_db($this->_database, $this->_conn);
if (!$db) {
throw new Exception(‘Cannot select the database : ‘ . mysql_error());
}
}

Like or Dislike: Thumb up 0 Thumb down 0

Leave a Comment

Previous post:

Next post: