Home arrow PHP arrow Overloading and Object-Oriented Programming with PHP 5

Overloading and Object-Oriented Programming with PHP 5

Last week, we discussed design patterns and polymorphism. This week, we examine overloading and more. This article, the last of four parts, is excerpted from chapter two of the book Advanced PHP Programming, written by George Schlossnagle (Sams; ISBN: 0672325616).

TABLE OF CONTENTS:
  1. Overloading and Object-Oriented Programming with PHP 5
  2. SPL and Interators
  3. _ _call()
  4. _ _autoload()
By: Sams Publishing
Rating: starstarstarstarstar / 16
October 12, 2006

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

Overloading

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=
\"mailto:$result->email\">$result->name</a>"; }

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++] =
$this->stmt->fetch_assoc(); } $this->currIndex = 0; return $this; } public function last() { if(!$this->done) { array_push($this->result,
$this->stmt->fetchall_assoc()); } $this->done = true; $this->currIndex = $this->rowIndex =
count($this->result) - 1; return $this; } public function next() { if($this->done) { return false; } $offset = $this->currIndex + 1; if(!$this->result[$offset]) { $row = $this->stmt->fetch_assoc(); if(!$row) { $this->done = true; return false; } $this->result[$offset] = $row; ++$this->rowIndex; ++$this->currIndex; return $this; } else { ++$this->currIndex; return $this; } } public function prev() { if($this->currIndex == 0) { return false; } --$this->currIndex; return $this; } }

The following are some things to note about DB_Result:

  • Its constructor uses a type hint to ensure that the variable passed to it is a DB_Statement object. Because your iterator implementations depend on $stmt complying with the DB_Statement API, this is a sanity check.

  • Results are lazy-initialized (that is, they are not created until they are about to be referenced). In particular, individual rows are only populated into DB_Result::result when the DB_Result object is iterated forward to their index in the result set; before that, no populating is performed. We will get into why this is important in Chapter 10, "Data Component Caching," but the short version is that lazy initialization avoids performing work that might never be needed until it is actually called for.

  • Row data is stored in the array DB_Result::result; however, the desired API had the data referenced as $obj->column, not $obj->result['column'], so there is still work left to do.

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:

  • function _ _get($varname) {}—This method is called when an undefined property is accessed for reading.

  • function _ _set($varname, $value) {}—This method is called when an undefined property is accessed for writing.

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",
"ndbm"); } function _ _destruct() { dba_close($this->dbm); } function _ _get($name) { $data = dba_fetch($name, $this->dbm); if($data) { print $data; return unserialize($data); } else { print "$name not found\n"; return false; } } function _ _set($name, $value) { dba_replace($name, serialize($value), $this->dbm); } }

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."
times.\n"; ?>

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))
called at [(null):3] #1 typed->unknown(name, Array ([0] => George))
called at [/Users/george/ Advanced PHP/examples/chapter-2/20.php:28]



 
 
>>> More PHP Articles          >>> More By Sams Publishing
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: