Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced PHP Programming

.pdf
Скачиваний:
67
Добавлен:
14.04.2015
Размер:
7.82 Mб
Скачать

58 Chapter 2 Object-Oriented Programming Through Design Patterns

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;

}

Overloading 59

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:

nIts 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.

60 Chapter 2 Object-Oriented Programming Through Design Patterns

nResults 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.

nRow 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:

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

nfunction _ _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.

Overloading

61

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);

62 Chapter 2 Object-Oriented Programming Through Design Patterns

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;

Overloading 63

}

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]

SPL and Interators

In both of the preceding examples, you created objects that you wanted to behave like arrays. For the most part, you succeeded, but you still have to treat them as objects for access. For example, this works:

$value = $obj->name;

But this generates a runtime error:

$value = $obj[name];

Equally frustrating is that you cannot use the normal array iteration methods with them. This also generates a runtime error:

foreach($obj as $k => $v) {}

To enable these syntaxes to work with certain objects, Marcus Boerger wrote the Standard PHP Library (SPL) extension for PHP5. SPL supplies a group of interfaces, and

64 Chapter 2 Object-Oriented Programming Through Design Patterns

it hooks into the Zend Engine, which runs PHP to allow iterator and array accessor syntaxes to work with classes that implement those interfaces.

The interface that SPL defines to handle array-style accesses is represented by the following code:

interface ArrayAccess {

function offsetExists($key);

function offsetGet($key);

function offsetSet($key, $value);

function offsetUnset($key);

}

Of course, because it is defined inside the C code, you will not actually see this definition, but translated to PHP, it would appear as such.

If you want to do away with the OO interface to Tied completely and make its access operations look like an arrays, you can replace its _ _get() and _ _set() operations as follows:

function offsetGet($name)

{

$data = dba_fetch($name, $this->dbm); if($data) {

return unserialize($data);

}

else {

return false;

}

}

function offsetExists($name)

{

return dba_exists($name, $this->dbm);

}

function offsetSet($name, $value)

{

return dba_replace($name, serialize($value), $this->dbm);

}

function offsetUnset($name)

{

return dba_delete($name, $this->dbm);

}

Now, the following no longer works because you removed the overloaded accessors:

$obj->name = George; // does not work

But you can access it like this:

$obj[name] = George;

Overloading 65

If you want your objects to behave like arrays when passed into built-in array functions (e.g., array map( )) you can implement the Iterator and IteratorAggregate interfaces, with the resultant iterator implementing the necessary interfaces to provide support for being called in functions which take arrays as parameters. Here’s an example:

interface IteratorAggregate { function getIterator();

}

interface Iterator {

function rewind();

function hasMore();

function key();

function current();

function next();

}

In this case, a class stub would look like this:

class KlassIterator implemnts Iterator { /* ... */

}

class Klass implements IteratorAggregate { function getIterator() {

return new KlassIterator($this);

}

/* ... */

}

The following example allows the object to be used not only in foreach() loops, but in for() loop as well:

$obj = new Klass;

for($iter = $obj->getIterator(); $iter->hasMore(); $iter = $iter->next())

{

// work with $iter->current()

}

In the database abstraction you wrote, you could modify DB_Result to be an iterator. Here is a modification of DB_Result that changes it’s API to implement Iterator:

class DB_Result {

protected $stmt;

protected $result = array();

66 Chapter 2 Object-Oriented Programming Through Design Patterns

protected $rowIndex = 0; protected $currIndex = 0; protected $max = 0; protected $done = false;

function _ _construct(DB_Statement $stmt)

{

$this->stmt = $stmt;

}

function rewind() { $this->currIndex = 0;

}

function hasMore() {

if($this->done && $this->max == $this->currIndex) { return false;

}

return true;

}

function key() {

return $this->currIndex;

}

function current() {

return $this->result[$this->currIndex];

}

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; $this->max = $this->currIndex; return false;

}

$this->result[$offset] = $row; ++$this->rowIndex; ++$this->currIndex;

return $this;

}

else { ++$this->currIndex; return $this;

}

}

}

Overloading 67

Additionally, you need to modify MysqlStatement to be an IteratorAggregate, so that it can be passed into foreach() and other array-handling functions. Modifying MysqlStatement only requires adding a single function, as follows:

class MysqlStatement implements IteratorAggregate { function getIterator() {

return new MysqlResultIterator($this);

}

}

If you don’t want to create a separate class to be a class’s Iterator, but still want the fine-grain control that the interface provides, you can of course have a single class implement both the IteratorAggregate and Iterator interfaces.

For convenience, you can combine the Iterator and Array Access interfaces to create objects that behave identically to arrays both in internal and user-space functions.This is ideal for classes like Tied that aimed to pose as arrays. Here is a modification of the Tied class that implements both interfaces:

class Tied implements ArrayAccess, Iterator { private $dbm;

private $dbmFile; private $currentKey;

function _ _construct($file = false)

{

$this->dbmFile = $file;

$this->dbm = dba_popen($this->dbmFile, w, ndbm);

}

function _ _destruct()

{

dba_close($this->dbm);

}

function offsetExists($name)

{

return dba_exists($name, $this->dbm);

}

function _ _offsetGet($name)

{

$data = dba_fetch($name, $this->dbm); if($data) {

return unserialize($data);

}

else {

return false;

}

}

function _offsetSet($name, $value)

{

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]