Let's bring together some of the techniques developed so far in this chapter and use overloading to provide a more OO-style interface to the result set. Having all the results in a single object may be a familiar paradigm to programmers who are used to using Java's JDBC database connectivity layer. Specifically, you want to be able to do the following: $query = "SELECT name, email FROM users";
$dbh = new DB_Mysql_Test;
$stmt = $dbh->prepare($query)->execute();
$result = $stmt->fetch();
while($result->next()) {
echo "<a href=
The code flow proceeds normally until after execution of the query. Then, instead of returning the rows one at a time as associative arrays, it would be more elegant to return a result object with an internal iterator that holds all the rows that have been seen. Instead of implementing a separate result type for each database that you support through the DB_Connection classes, you can exploit the polymorphism of the statement's classes to create a single DB_Result class that delegates all its platform-specific tasks to the DB_Statement object from which it was created. DB_Result should possess forward and backward iterators, as well as the ability to reset its position in the result set. This functionality follows easily from the techniques you've learned so far. Here is a basic implementation of DB_Result: class DB_Result {
protected $stmt;
protected $result = array();
private $rowIndex = 0;
private $currIndex = 0;
private $done = false;
public function _ _construct(DB_Statement $stmt)
{
$this->stmt = $stmt;
}
public function first()
{
if(!$this->result) {
$this->result[$this->rowIndex++] =
The following are some things to note about DB_Result:
The difficult part in using an OO interface to result sets is providing access to the column names as properties. Because you obviously cannot know the names of the columns of any given query when you write DB_Result, you cannot declare the columns correctly ahead of time. Furthermore, because DB_Result stores all the rows it has seen, it needs to store the result data in some sort of array (in this case, it is DB_Result::result). Fortunately, PHP provides the ability to overload property accesses via two magical methods:
In this case, DB_Result needs to know that when a result set column name is accessed, that column value in the current row of the result set needs to be returned. You can achieve this by using the following _ _get function, in which the single parameter passed to the function is set by the system to the name of the property that was being searched for: public function _ _get($varname)
{
if(array_key_exists($value,
$this->result[$this->currIndex])) {
return $this->result[$this->currIndex][$value];
}
}
Here you check whether the passed argument exists in the result set. If it does, the accessor looks inside $this->result to find the value for the specified column name. Because the result set is immutable (that is, you cannot change any of the row data through this interface), you don't need to worry about handling the setting of any attributes. There are many other clever uses for property overriding abilities. One interesting technique is to use _ _get() and _ _set() to create persistent associative arrays that are tied to a DBM file (or other persistent storage). If you are familiar with Perl, you might liken this to using tie() in that language. To make a persistent hash, you create a class called Tied that keeps an open handle to a DBM file. (DBM files are explored in depth in Chapter 10.) When a read request is initiated on a property, that value is fetched from the hash and deserialized (so that you can store complex data types). A write operation similarly serializes the value that you are assigning to the variable and writes it to the DBM. Here is an example that associates a DBM file with an associative array, making it effectively a persistent array (this is similar to a Tied hash in Perl): class Tied {
private $dbm;
private $dbmFile;
function _ _construct($file = false)
{
$this->dbmFile = $file;
$this->dbm = dba_popen($this->dbmFile, "c",
Now you can have an associative array type of object that allows for persistent data, so that if you use it as: <?
$a = new Tied("/tmp/tied.dbm");
if(!$a->counter) {
$a->counter = 0;
}
else {
$a->counter++;
}
print "This page has been accessed ".$a->counter."
each access increments it by one: > php 19.php This page has been accessed 1 times. > php 19.php This page has been accessed 2 times. Overloading can also be used to provide access controls on properties. As you know, PHP variables can be of any type, and you can switch between types (array, string, number, and so on) without problems. You might, however, want to force certain variables to stay certain types (for example, force a particular scalar variable to be an integer). You can do this in your application code: You can manually validate any data before a variable is assigned, but this can become cumbersome, requiring a lot of duplication of code and allowing for the opportunity for forgetting to do so. By using _ _get() and _ _set(), you can implement type checking on assignment for certain object properties. These properties won't be declared as standard attributes; instead, you will hold them in a private array inside your object. Also, you will define a type map that consists of variables whose types you want to validate, and you will define the function you will use to validate their types. Here is a class that forces its name property to be a string and its counter property to be an integer: class Typed {
private $props = array();
static $types = array (
"counter" => "is_integer",
"name" => "is_string"
);
public function _ _get($name) {
if(array_key_exists($name, $this->props)) {
return $this->props[$name];
}
}
public function _ _set($name,$value) {
if(array_key_exists($name, self::$types)) {
if(call_user_func(self::$types[$name],$value)) {
$this->props[$name] = $value;
}
else {
print "Type assignment error\n";
debug_print_backtrace();
}
}
}
}
When an assignment occurs, the property being assigned to is looked up in self::$types, and its validation function is run. If you match types correctly, everything works like a charm, as you see if you run the following code: $obj = new Typed; $obj->name = "George"; $obj->counter = 1; However, if you attempt to violate your typing constraints (by assigning an array to $obj->name, which is specified of type is_string), you should get a fatal error. Executing this code: $obj = new Typed;
$obj->name = array("George");
generates the following error: > php 20.php Type assignment error #0 typed->_ _set(name, Array ([0] => George))
blog comments powered by Disqus |
|
|
|
|
|
|
|