Building a Singleton Database with Restrictive Constructors in PHP 5

In this third installment of a four-part series, I show you that a private constructor can be truly helpful when strictly implementing the Singleton design pattern. In this case, the pattern will be applied within a class that behaves like a simple MySQL abstraction layer, but the same concept can be extended to other classes.

At first sight, declaring a constructor method protected or private in PHP 5 seems to be a rather irrelevant and even boring topic. The process doesn’t differ too much from specifying one of those levels of restriction for a regular class method. However, this first impression may be misleading.

Of course, I’m not saying that using a restrictive constructor is going to change forever the way that you build your own classes, but there are a number of situations where making a constructor directly inaccessible from the outside can be much more helpful than you may think. To cite a few examples, a typical implementation of a Singleton class often requires coding a private/protected constructor, and this approach can also be used when coding classes that are meant to be accessed exclusively out of the object context.

Due to the intrinsic nature of constructors, imposing a more rigid access level on them means controlling the instantiation of classes in a given context. That’s  pretty easy to see from the examples mentioned above.

Given the importance that restrictive constructor have in PHP 5, the subject deserves a close look from a practical and pragmatic perspective. In consonance with this requisite, in the two previous chapters of this series I demonstrated a basic use of a protected constructor within an array iterator class, to prevent its direct instantiation. Even so, this introductory example was pretty trivial, as the same level of restriction could be easily achieved by declaring the iterator abstract.

However, in this third tutorial I’m going to create a more realistic example for you. It will make use of a private constructor in a more useful way, since this approach will be utilized for developing a Singleton class which will act like a simple abstraction layer to MySQL.

Are you feeling eager to learn the details of how this sample class will be built? Then start reading right now!

{mospagebreak title=Building a MySQL abstraction class} 

As I expressed in the introduction, my goal in this tutorial will consist of demonstrating how to use a private constructor within a class that will abstract the access to MySQL. In simple terms, this sample class will implement a Singleton static method, which as you may guess, will permit it to spawn only one instance of the MySQL driver.

Undoubtedly, a database abstraction class is really a good candidate for turning into a Singleton. Having said that, take a look at the following code fragment, which shows the partial definition of the class in question. Here it is:     

class MySQL

{

    private $_result = NULL;

    private $_link = NULL;

    private $_config = array();

    private static $_instance = NULL;

 

 

 

    // return Singleton instance of MySQL class

    public static function getInstance(array $config = array())

    {

        if (self::$_instance === NULL)

        {

            self::$_instance = new self($config);

        }

        return self::$_instance;

    }

   

    // private constructor

    private function __construct(array $config)

    {

        if (count($config) < 4)

        {

            throw new Exception(‘Invalid number of connection parameters’);  

        }

        $this->_config = $config;

    }

   

    // prevent cloning class instance

    private function __clone(){}

}

As seen above, the “MySQL” abstraction class implements for the moment only two methods. The first one is the static “getInstance(),” which is responsible for returning to client code a Singleton instance of the class. On the other hand, the second one is the constructor that accepts an array of parameters required for connecting to the server and selecting a specified database.

So far, the constructor has nothing particularly special, except for a tiny subtlety: it’s been declared private. The reason for doing so is self-explanatory: by preventing the constructor from being called from the outside, the unique access point to a single instance of the class is the aforementioned “getInstance()” method. That was simple to grasp, right?

In addition, to give the Singleton pattern a stricter implementation, the magic “__clone()” PHP 5 method has also been defined private, a process that avoids directly cloning instances of the class.

Even though at this point the earlier MySQL driver is only composed of a few straightforward methods, it’s handy for illustrating a typical case where a private constructor can be of great help concerning the application of a specific design pattern.

Nonetheless, the driver in its current state isn’t very useful, meaning that it’s necessary to add more methods to it that extend its limited functionality. With that idea in mind, in the following section I’m going to code some additional methods. They will give the driver the ability to connect lazily to the server and run queries, fetch database rows and a few more useful tasks.

To see how these brand new methods will be defined, click on the link below and read the section to come.

{mospagebreak title=Adding methods: fetching rows, finding insertion IDs and more} 

Provided that you already understood the rationale behind declaring the constructor of the previous MySQL driver private, it’s time to extend its current functionality. But how will this be done? Simply by our adding a few additional methods to it, the class will be able to establish a connection to the server, run queries and so forth, through a friendly interface.

Having explained that, below I listed the full definition of the driver, including the extra methods that will perform the aforementioned tasks:

class MySQL

{

    private $_result = NULL;

    private $_link = NULL;

    private $_config = array();

    private static $_instance = NULL;

 

 

 

    // return Singleton instance of MySQL class

    public static function getInstance(array $config = array())

    {

        if (self::$_instance === NULL)

        {

            self::$_instance = new self($config);

        }

        return self::$_instance;

    }

   

    // private constructor

    private function __construct(array $config)

    {

        if (count($config) < 4)

        {

            throw new Exception(‘Invalid number of connection parameters’);  

        }

        $this->_config = $config;

    }

   

    // prevent cloning class instance

    private function __clone(){}

   

    // connect to MySQL

    private function connect()

    {

        // connect only once

        static $connected = FALSE;

        if ($connected === FALSE)

        {

            list($host, $user, $password, $database) = $this->_config;

            if ((!$this->_link = mysqli_connect($host, $user, $password, $database)))

            {

                throw new Exception(‘Error connecting to MySQL : ‘ . mysqli_connect_error());

            }

            $connected = TRUE;

            unset($host, $user, $password, $database);      

        }

    }

 

 

 

    // perform query

    public function query($query)

    {

        if (is_string($query) and !empty($query))

        {

            // lazy connect to MySQL

            $this->connect();

            if ((!$this->_result = mysqli_query($this->_link, $query)))

            {

                throw new Exception(‘Error performing query ‘ . $query . ‘ Message : ‘ . mysqli_error($this->_link));

            }

        }

    }

   

    // fetch row from result set

    public function fetch()

    {

        if ((!$row = mysqli_fetch_object($this->_result)))

        {

            mysqli_free_result($this->_result);

            return FALSE;

        }

        return $row;

    }

 

 

 

    // get insertion ID

    public function getInsertID()

    {

        if ($this->_link !== NUlL)

        {

            return mysqli_insert_id($this->_link); 

        }

        return NULL;

       

    }

   

    // count rows in result set

    public function countRows()

    {

        if ($this->_result !== NULL)

        {

           return mysqli_num_rows($this->_result);

        }

        return 0;

    }

   

    // close the database connection

    function __destruct()

    {

        is_resource($this->_link) AND mysqli_close($this->_link);

    }

}// End MySQL class

Done. As you can see, at this point the above “MySQL” class not only exposes a single entry point through its Singleton “getInstance()” method, but it defines some simple methods for executing queries against the selected database, fetching and counting rows, and finding insertion IDs as well.

Also, it’s worth noting the implementation of a method called “connect()” that takes care of connecting to MySQL in a lazy way, meaning that it’ll be run once, and only when the driver performs a given query.

So far, so good. At this moment, the previous MySQL driver still isn’t suitable to use in a production environment, but it definitely it looks much more functional than it did in an earlier stage. Thus, it’s time to set up an example that shows how to use it in a concrete case, so you can see how it works.

This hands-on example will be coded in the last section of the tutorial. So click on the link that appears below and keep reading.

{mospagebreak title=Putting the MySQL driver to work} 

As I promised in the section that you just read, in the lines to come I developed a basic example aimed at demonstrating how to work with the prior MySQL abstraction class. First, I included the full definition of the driver, accompanied by the corresponding code sample. Take a look, please:

class MySQL

{

    private $_result = NULL;

      private $_link = NULL;

    private $_config = array();

    private static $_instance = NULL;

 

 

 

    // return Singleton instance of MySQL class

    public static function getInstance(array $config = array())

    {

        if (self::$_instance === NULL)

        {

            self::$_instance = new self($config);

        }

        return self::$_instance;

    }

   

    // private constructor

    private function __construct(array $config)

    {

        if (count($config) < 4)

        {

            throw new Exception(‘Invalid number of connection parameters’);  

        }

        $this->_config = $config;

    }

   

    // prevent cloning class instance

    private function __clone(){}

   

    // connect to MySQL

    private function connect()

    {

        // connect only once

        static $connected = FALSE;

        if ($connected === FALSE)

        {

            list($host, $user, $password, $database) = $this->_config;

            if ((!$this->_link = mysqli_connect($host, $user, $password, $database)))

            {

                throw new Exception(‘Error connecting to MySQL : ‘ . mysqli_connect_error());

            }

            $connected = TRUE;

            unset($host, $user, $password, $database);      

        }

    }

 

 

 

    // perform query

    public function query($query)

    {

        if (is_string($query) and !empty($query))

        {

            // lazy connect to MySQL

            $this->connect();

            if ((!$this->_result = mysqli_query($this->_link, $query)))

            {

                throw new Exception(‘Error performing query ‘ . $query . ‘ Message : ‘ . mysqli_error($this->_link));

            }

        }

    }

   

    // fetch row from result set

    public function fetch()

    {

        if ((!$row = mysqli_fetch_object($this->_result)))

        {

            mysqli_free_result($this->_result);

            return FALSE;

        }

        return $row;

    }

 

 

 

    // get insertion ID

    public function getInsertID()

    {

        if ($this->_link !== NUlL)

        {

            return mysqli_insert_id($this->_link); 

        }

        return NULL;

       

    }

   

    // count rows in result set

    public function countRows()

    {

        if ($this->_result !== NULL)

        {

           return mysqli_num_rows($this->_result);

        }

        return 0;

    }

   

    // close the database connection

    function __destruct()

    {

        is_resource($this->_link) AND mysqli_close($this->_link);

    }

}// End MySQL class

 

 

 

 

 

 

// create instance of MySQL class

$db = MySQL::getInstance(array(‘host’, ‘user’, ‘password’, ‘database’));

// fetch users from database table

$db->query(‘SELECT * FROM users’);

// display user data

while ($user = $db->fetch())

{

    echo ‘First Name: ‘ . $user->fname . ‘ Last Name: ‘. $user->lname . ‘ Email: ‘. $user->email . ‘<br />';

}

In the above script, the reason for making the constructor of the MySQL driver private becomes evident, as an instance of the class is only grabbed through its “getInstance()” static method. One this has been done, the corresponding instance is used to fetch some records from a “users” table, and finally the data is echoed to the browser in a standard fashion.

If you grasped the underlying logic of the previous example, at this point you have a better idea of how to use a private constructor in a more realistic situation. In this particular case, the method was employed to build a Singleton class, but there are many other situations where restrictive constructors can be quite helpful as well.    

Final thoughts

That’s all for now. In this third installment of the series, I showed you that a private constructor can be truly helpful when it comes to more strictly implementing the Singleton design pattern. In this concrete case, the pattern was applied within a class that behaved like a simple MySQL abstraction layer, but as you may guess the same concept can be extended to other classes as well.

In the next tutorial, I’m going to provide you with yet another example regarding the use of a private constructor in PHP 5, which this time will be utilized for building a purely-static HTML form helper class.

Here’s my little piece of advice: don’t miss the forthcoming part!

[gp-comments width="770" linklove="off" ]

chat