Working in the Method Scope with Static Variables in PHP 5 Classes

Despite their name, static properties can help in a wide variety of situations. If you’re not using them currently, and you regularly implement design patterns in your code, after reading this series you’ll wonder how you ever did without them. This article is the conclusion to a four-part series.

The term “static” is misleading when applied to a property of a class in PHP 5. It may lead you to think that a property like this does nothing particularly useful, or simply that its functionality is so irrelevant that it can’t be considered a truly dynamic in-memory place.

Fortunately, this initial misconception is far from reality. Static properties can be really helpful in a huge variety of scenarios and situations, especially for  implementing a specific design pattern or improving the behavior of their containing classes.

Once the theoretical concepts that define the meaning of static properties have been properly mastered and digested, quite possibly the most challenging task that must be faced is to demonstrate how they can be used in a real-word context, other than appealing to the famous (or infamous) toy code.

That’s exactly the goal of this series of articles, in which you’ve been walked through the development of a functional object-oriented MySQL driver. The driver makes use of a couple of static properties for turning one of its classes into a Singleton, and also for improving the way that it establishes a connection to the database server.

In fact, I left off the last installment showing how a simple static property can be employed within the driver to avoid connecting to MySQL multiple times, thus fixing a serious performance issue. As I said before, this was accomplished through a single class-level variable explicitly declared inside its originating class.

However, it’s also possible to get the same result by using an undeclared property within the method responsible for connecting to MySQL. In this last part of the series I’m going to explore this approach in depth, backed up with a decent variety of code samples.

Now, it’s time to tackle the final chapter of this educational journey on using static variables in PHP 5 classes. Let’s go!

{mospagebreak title=Review: the MySQL driver so far}

Before showing you how an undeclared static property can stop the MySQL driver built in the previous part from unnecessarily connecting multiple times to the database server, I’d like to review the definitions of the classes that compose the driver.

Here’s the first of these classes. It is a wrapper for the native MySQLi class included in PHP 5. Look at it, please:

class MySQLiWrapper extends MySQLi

{

 

private static $_instance = NULL;

private static $_connected = FALSE;

private $_config = array();

 

// 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(){}

 

// establish a connection to MySQL

public function connect()

{

// if no previous connection exits, connect to MySQL

if (self::$_connected === FALSE)

{

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

parent::__construct($host, $user, $password, $database);

if ($this->connect_error)

{

throw new Exception(‘Error connecting to MySQL : ‘ . $this->connect_errno . ‘ ‘ . $this->connect_error);

}

// connection is successful

self::$_connected = TRUE;

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

}

}

 

// perform query

public function runQuery($query)

{

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

{

// lazy connect to MySQL

$this->connect();

// run the specified query

if ((!$this->real_query($query)))

{

throw new Exception(‘Error performing query ‘ . $query . ‘ – Error message : ‘ . $this->error);

}

return new MySQLi_ResultWrapper($this);

}

}

 

// get insertion ID

public function getInsertID()

{

return $this->insert_id;

}

 

// close database connection

public function __destruct()

{

$this->close();

}

}

Aside from performing a few straightforward tasks, such as running hard-coded SQL queries and finding insertion IDs, the above “MySQLiWrapper” class can lazily connect to MySQL. This means that its “connect()” method will be called only once when performing a given query, thanks to the use of a static private property named “$_connected.”

This shows a concrete usage of a static property within a class that can be utilized in a pretty realistic situation. Speaking of classes, here’s the other one that comprises the driver. It handles result sets:

class MySQLi_ResultWrapper extends MySQLi_Result implements Iterator, Countable

{

private $_pointer = 0;

 

// fetch row as an object

public function fetchObject()

{

if (!$row = $this->fetch_object())

{

return NULL;

}

return $row;

}

 

// fetch row as an associative array

public function fetchAssocArray()

{

if (!$row = $this->fetch_assoc())

{

return NULL;

}

return $row;

}

 

// fetch row as an enumerated array

public function fetchNumArray()

{

if (!$row = $this->fetch_row())

{

return NULL;

}

return $row;

}

 

// fetch all rows

public function fetchAll($type = MYSQLI_ASSOC)

{

if ($type !== MYSQLI_ASSOC AND $type !== MYSQLI_NUM AND $type !== MYSQLI_BOTH)

{

$type = MYSQLI_ASSOC;

}

if (!$rows = $this->fetch_all($type))

{

return NULL;

}

return $rows;

}

 

// get definition information on fields

public function fetchFieldsInfo()

{

if (!$fieldsInfo = $this->fetch_fields())

{

throw new Exception(‘No information available for table fields.’);

}

return $fieldsInfo;

}

 

// get definition information on next field

public function fetchFieldInfo()

{

if (!$fieldInfo = $this->fetch_field())

{

throw new Exception(‘No information available for current table field.’);

}

return $fieldInfo;

}

 

// move pointer in result set to specified offset

public function movePointer($offset)

{

$offset = abs((int)$offset);

$limit = $this->num_rows – 1;

if ($limit <= 0 OR $offset > $limit)

{

return FALSE;

}

unset($limit);

return $this->data_seek($offset);

}

 

// count rows in result set (implementation required by ‘count()’ method in Countable interface)

public function count()

{

return $this->num_rows;

}

 

// reset result set pointer (implementation required by ‘rewind()’ method in Iterator interface)

public function rewind()

{

$this->_pointer = 0;

$this->movePointer($this->_pointer);

return $this;

}

 

// get current row set in result set (implementation required by ‘current()’ method in Iterator interface)

public function current()

{

if (!$this->valid())

{

throw new Exception(‘Unable to retrieve current row.’);

}

$this->movePointer($this->_pointer);

return $this->fetchObject();

}

 

// get current result set pointer (implementation required by ‘key()’ method in Iterator interface)

public function key()

{

return $this->_pointer;

}

 

// move forward result set pointer (implementation required by ‘next()’ method in Iterator interface)

public function next()

{

++$this->_pointer;

$this->movePointer($this->_pointer);

return $this;

}

 

// determine if result set pointer is valid or not (implementation required by ‘valid()’ method in Iterator interface)

public function valid()

{

return $this->_pointer < $this->num_rows;

}

 

// free up result set

public function __destruct()

{

$this->close();

}

}

To be frank, you shouldn’t feel intimidated by the lengthy definition of this class. It fetches rows from a given result set in different flavors (as objects, numerically-indexed or associative arrays) via a simple API. On the other hand, most of the remaining methods are implemented simply to make a data sets traversable by a “foreach” construct, as the class is an implementer of the “Iterator” and “Countable” native interfaces respectively.

And now that you’ve recalled how the MySQL drivers does its business, here’s an example that shows how to use it for pulling data from a sample “users” database table:

<?php

 

try

{

// connect to MySQL

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

 

// fetch users from database

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

 

// display user data

foreach ($users as $user)

{

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

}

}

// catch exceptions

catch(Exception $e)

{

echo $e->getMessage();

exit();

}

Even though at first sight the above example seems pretty trivial, it does demonstrate how the internal use of a static property can prevent an instance of the “MySQLiWrapper” class to connect to MySQL each time it performs a specified SQL query. That wasn’t too difficult to grasp, was it?

As I explained at the beginning, it’s also possible to get the same benefit by using a static variable not declared explicitly. As you may know, whenever a static variable is created inside a method of a class, it becomes automatically available to all of the spawned instances. Clever use of this knowledge can be of great help in preventing unnecessary connections to a RDBMS.

Based on this intrinsic behavior of static variables, in the following section I’m going to introduce some subtle changes to the previous “MySQLiWrapper” class. As you’ll see in a moment, it will make use of an undeclared static variable to connect only once to MySQL.

To learn how this will be accomplished, read the segment to come. It’s only one click away.

{mospagebreak title=Using a local static variable}

As I said in the segment you just read, it’s perfectly possible to use an undeclared static property within the “MySQLiWrapper” class to prevent multiple, unnecessary connections to MySQL. But how will this be done? Creating the mentioned variable only within the “connect()” method will do the trick.

If this sounds a bit confusing to you, then study the modified version of the class, which now looks like this:

 

class MySQLiWrapper extends MySQLi

{

 

private static $_instance = NULL;

private $_config = array();

 

// 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(){}

 

// establish a connection to MySQL

public function connect()

{

static $connected = FALSE;

// if no previous connection exists, connect to MySQL

if ($connected === FALSE)

{

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

parent::__construct($host, $user, $password, $database);

if ($this->connect_error)

{

throw new Exception(‘Error connecting to MySQL : ‘ . $this->connect_errno . ‘ ‘ . $this->connect_error);

}

// connection is successful

$connected = TRUE;

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

}

}

 

// perform query

public function runQuery($query)

{

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

{

// lazy connect to MySQL

$this->connect();

// run the specified query

if ((!$this->real_query($query)))

{

throw new Exception(‘Error performing query ‘ . $query . ‘ – Error message : ‘ . $this->error);

}

return new MySQLi_ResultWrapper($this);

}

}

 

// get insertion ID

public function getInsertID()

{

return $this->insert_id;

}

 

// close database connection

public function __destruct()

{

$this->close();

}

}

Since most of the methods that compose the “MySQLiWrapper” class remain the same, I suggest you focus your attention only on the “connect()” method. This is the one that has been modified. In this case, the method internally creates a static variable called $connected, which is used as a flag to connect to MySQL only once.

Even though this variable is brought to life within this specific method, its scope is still class-level, meaning that it behaves exactly as if it was a declared static class property.

On the other hand, the source code of the “MySQLi_ResultWrapper” class remains entirely unchanged, but for the sake of completeness, I included its definition below once again:

class MySQLi_ResultWrapper extends MySQLi_Result implements Iterator, Countable

{

private $_pointer = 0;

 

// fetch row as an object

public function fetchObject()

{

if (!$row = $this->fetch_object())

{

return NULL;

}

return $row;

}

 

// fetch row as an associative array

public function fetchAssocArray()

{

if (!$row = $this->fetch_assoc())

{

return NULL;

}

return $row;

}

 

// fetch row as an enumerated array

public function fetchNumArray()

{

if (!$row = $this->fetch_row())

{

return NULL;

}

return $row;

}

 

// fetch all rows

public function fetchAll($type = MYSQLI_ASSOC)

{

if ($type !== MYSQLI_ASSOC AND $type !== MYSQLI_NUM AND $type !== MYSQLI_BOTH)

{

$type = MYSQLI_ASSOC;

}

if (!$rows = $this->fetch_all($type))

{

return NULL;

}

return $rows;

}

 

// get definition information on fields

public function fetchFieldsInfo()

{

if (!$fieldsInfo = $this->fetch_fields())

{

throw new Exception(‘No information available for table fields.’);

}

return $fieldsInfo;

}

 

// get definition information on next field

public function fetchFieldInfo()

{

if (!$fieldInfo = $this->fetch_field())

{

throw new Exception(‘No information available for current table field.’);

}

return $fieldInfo;

}

 

// move pointer in result set to specified offset

public function movePointer($offset)

{

$offset = abs((int)$offset);

$limit = $this->num_rows – 1;

if ($limit <= 0 OR $offset > $limit)

{

return FALSE;

}

unset($limit);

return $this->data_seek($offset);

}

 

// count rows in result set (implementation required by ‘count()’ method in Countable interface)

public function count()

{

return $this->num_rows;

}

 

// reset result set pointer (implementation required by ‘rewind()’ method in Iterator interface)

public function rewind()

{

$this->_pointer = 0;

$this->movePointer($this->_pointer);

return $this;

}

 

// get current row set in result set (implementation required by ‘current()’ method in Iterator interface)

public function current()

{

if (!$this->valid())

{

throw new Exception(‘Unable to retrieve current row.’);

}

$this->movePointer($this->_pointer);

return $this->fetchObject();

}

 

// get current result set pointer (implementation required by ‘key()’ method in Iterator interface)

public function key()

{

return $this->_pointer;

}

 

// move forward result set pointer (implementation required by ‘next()’ method in Iterator interface)

public function next()

{

++$this->_pointer;

$this->movePointer($this->_pointer);

return $this;

}

 

// determine if result set pointer is valid or not (implementation required by ‘valid()’ method in Iterator interface)

public function valid()

{

return $this->_pointer < $this->num_rows;

}

 

// free up result set

public function __destruct()

{

$this->close();

}

}

At this point, the “MySQLiWrapper” class internally uses a static variable created within its “connect()” method to avoid connecting to MySQL each time it performs a query. Obviously, this is the expected and desired behavior of the class, but to demonstrate that it really works this way, in the following section I’m going to build a script that will show how to use the modified version of this sample MySQL driver.

Read the next segment. We’re almost finished. 

{mospagebreak title=Putting the MySQL driver to work}

If you’re like me, then I’m certain that you’ll want to see an example that illustrates how the previous MySQL driver works. With that idea in mind, below I coded a basic script that runs two queries against a fictional “users” MySQL table. As expected, the connection to the database server is made only once:

<?php

 

try

{

// connect to MySQL

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

 

// fetch users from database

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

 

// display user data

foreach ($users as $user)

{

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

}

 

// fetch users’ first names from database

$users = $db->runQuery(‘SELECT fname FROM users’);

 

// display user data

foreach ($users as $user)

{

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

}

 

}

// catch exceptions

catch(Exception $e)

{

echo $e->getMessage();

exit();

}

As the above script shows, it’s now possible to run multiple queries against a selected database table without having to spawn multiple connections to MySQL, by using a simple static variable created inside a single method. From this point on, feel free to edit and improve the definitions of the classes shown in this tutorial. This process will surely give you more ideas for how to use static properties in new and creative ways.

Final thoughts

It’s hard to believe, but we’ve come to the end of the series. Hopefully, this short exploration of the use of static properties within PHP 5 classes has been helpful to you, as in the course of the series you learned how to employ them within a class that can be used as a simple, yet functional MySQL abstraction layer.

Ranging from implementing the Singleton and Lazy Loading design patterns, to improving the individual behavior of methods, static variables can be really handy. However, as with many other features offered by PHP 5, they should be used with caution and consciously.

See you in the next PHP tutorial!

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

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort