The object-oriented programming features introduced by www. ibm. comdeveloperworksxmllibraryos-php-flexobjPHP5 significantly improved the PHP language hierarchy. It's not just the access control of members and Methods. private, protected, public -- is the same as Java, C ++, or C # -- you can also create objects that can be dynamically changed during running,
The object-oriented programming feature introduced by http://www.ibm.com/developerworks/xml/library/os-php-flexobj/ PHP5 significantly enhances the PHP language layer. It's not just the access control of members and Methods. private, protected, public -- is the same as Java, C ++, or C # -- you can also create objects that can be dynamically changed during running,
Http://www.ibm.com/developerworks/xml/library/os-php-flexobj/
The object-oriented programming features introduced by PHP5 significantly increase the PHP language layer. It's not just the access control of members and Methods. private, protected, public -- is the same as Java, C ++, or C # -- you can also create objects that can be dynamically changed during running, dynamically create a new method and attribute. These are not available in Java, C ++, or C. This language capability makes it possible to develop a fast framework like ROR.
Before proceeding to the topic, let us know: This article involves the use of very advanced OOP features in PHP5, which are not used in all applications. Moreover, these features are hard to understand. If you do not have a deep understanding of OOP, you should at least have a preliminary understanding of the PHP Object syntax.
Importance of dynamic Features The importance of being dynamic
Objects are a double-edged sword. On the one hand, an object is a good way to encapsulate data and logic. It is more intuitive and easy to maintain to create a system. On the other hand, it may also cause class expansion, and the code becomes trivial. You need to write a lot of repetitive code, and you can only expect no mistakes in the middle. A typical example of this problem is database access and operation objects. Generally, you need to create a simple class for each table and provide methods such as CRUD. However, there are other ways to create an empty object, set properties, and then insert the database.
For example, if you have a Customers table, you have to create a Customer object with the attributes corresponding to the fields in the table, representing a single Customer. This Customer object allows you to insert, update, or delete the corresponding database records. This seems good, can work, but there is a bunch of code to write. If you have 20 tables, you need to write 20 classes.
There are three ways to solve this problem. The first method is copy/paste/modify. For small projects, this is nothing. The second method is to use the code generator to read the database solution and then automatically generate code for you. Many development frameworks provide code generators, which is a good idea. The third method, which is described in this article, is to compile a simple class and dynamically model it based on the field of the given table at runtime. This is similar to the performance of the model class of a specific table, but the code is much simpler. This method is especially cost-effective for projects with frequent design changes.
So how to write a flexible class?
Write a flexible class Writing a bendy class
The object has two aspects: member variables and methods. For traditional compilation languages such as Java, if you want to call a nonexistent method or a nonexistent member variable, a compilation error is reported. What happens to PHP?
A PHP method call works as follows. First, check the methods of this class in the PHP interpreter. If the methods exist, call them directly. Otherwise, call the magic function _ call of this class. If this method exists, if the call fails, call the method of the parent class, and so on.
A magic function is a named method that is triggered by the PHP interpreter on a specific node executed by the script.
The most common magic function is the constructor called when an object is created.
The _ call method has two parameters: the request method name and parameter. Using the _ call mechanism and writing a method by yourself, the effect is the same for function callers. In this way, you can create an object that contains "countless" methods.
In addition to the _ call method, other magic functions, such as _ get and _ set, are called when reference to nonexistent instance variables. Remember this, you can start to write a class that applies to any database table access.
(The following is mainly code refactoring to keep the original article)
Classic database access
Let's start with a simple database schema. The schema shown in Listing 1 is for a single data-table database that holds a list of books.
Listing 1. The MySQL database schema
Drop table if exists book;
Create table book (
Book_id int not null AUTO_INCREMENT,
Title TEXT,
Publisher TEXT,
Author TEXT,
Primary key (book_id)
);
Load this schema into a database named bookdb.
Next, write a conventional database class that you will then modify to become dynamic. Listing 2 shows a simple database access class for the book table.
Listing 2. The basic database access client
getMessage()); }class Book{ private $book_id; private $title; private $author; private $publisher; function __construct() { } function set_title( $title ) { $this->title = $title; } function get_title( ) { return $this->title; } function set_author( $author ) { $this->author = $author; } function get_author( ) { return $this->author; } function set_publisher( $publisher ) { $this->publisher = $publisher; } function get_publisher( ) { return $this->publisher; } function load( $id ) { global $db;$res = $db->query( "SELECT * FROM book WHERE book_id=?", array( $id ) ); $res->fetchInto( $row, DB_FETCHMODE_ASSOC ); $this->book_id = $id; $this->title = $row['title']; $this->author = $row['author']; $this->publisher = $row['publisher']; } function insert() { global $db; $sth = $db->prepare('INSERT INTO book ( book_id, title, author, publisher ) VALUES ( 0, ?, ?, ? )' ); $db->execute( $sth, array( $this->title, $this->author, $this->publisher ) ); $res = $db->query( "SELECT last_insert_id()" ); $res->fetchInto( $row ); return $row[0]; } function update() { global $db; $sth = $db->prepare('UPDATE book SET title=?, author=?, publisher=? WHERE book_id=?' ); $db->execute( $sth, array( $this->title, $this->author, $this->publisher, $this->book_id ) ); } function delete() { global $db; $sth = $db->prepare( 'DELETE FROM book WHERE book_id=?' ); $db->execute( $sth, array( $this->book_id ) ); } function delete_all() { global $db; $sth = $db->prepare( 'DELETE FROM book' ); $db->execute( $sth ); }}$book = new Book();$book->delete_all();$book->set_title( "PHP Hacks" );$book->set_author( "Jack Herrington" );$book->set_publisher( "O'Reilly" );$id = $book->insert();echo ( "New book id = $id\n" );$book2 = new Book();$book2->load( $id );echo( "Title = ".$book2->get_title()."\n" );$book2->delete( );?>
To keep the code simple, I put the class and the test code in one file. the file starts with getting the database handle, which it stores in a global variable. the Book class is then defined, with private member variables for each field. A set of methods for loading, inserting, updating, and deleting rows from the database is also encoded.
The test code at the bottom starts by deleting all the entries from the database. next, the code inserts a book, telling you the ID of the new record. then, the code loads that book into another object and prints the title.
Listing 3 shows what happens when you run the code on the command line with the PHP interpreter.
Listing 3. Running the code on the command line
% Php db1.php
New book id = 25
Title = PHP Hacks
%
Not much to look at, but it gets the point into ss. the Book object represents a row in the book data table. by using the fields and the methods above, you can create new rows, update them, and delete them.
Introduce A little dynamic A little dab of dynamic
The next step is to make the class a bit dynamic by creating the get _ and set _ methods on the fly for the inpidual fields. Listing 4 shows the updated code.
Listing 4. Dynamic get _ and set _ methods
getMessage()); }class Book{ private $book_id; private $fields = array(); function __construct() { $this->fields[ 'title' ] = null; $this->fields[ 'author' ] = null; $this->fields[ 'publisher' ] = null; } function __call( $method, $args ) { if ( preg_match( "/set_(.*)/", $method, $found ) ) { if ( array_key_exists( $found[1], $this->fields ) ) { $this->fields[ $found[1] ] = $args[0]; return true; } } else if ( preg_match( "/get_(.*)/", $method, $found ) ) { if ( array_key_exists( $found[1], $this->fields ) ) { return $this->fields[ $found[1] ]; } } return false; } function load( $id ) { global $db;$res = $db->query( "SELECT * FROM book WHERE book_id=?", array( $id ) ); $res->fetchInto( $row, DB_FETCHMODE_ASSOC ); $this->book_id = $id; $this->set_title( $row['title'] ); $this->set_author( $row['author'] ); $this->set_publisher( $row['publisher'] ); } function insert() { global $db; $sth = $db->prepare('INSERT INTO book ( book_id, title, author, publisher ) VALUES ( 0, ?, ?, ? )' ); $db->execute( $sth, array( $this->get_title(), $this->get_author(), $this->get_publisher() ) ); $res = $db->query( "SELECT last_insert_id()" ); $res->fetchInto( $row ); return $row[0]; } function update() { global $db; $sth = $db->prepare('UPDATE book SET title=?, author=?, publisher=? WHERE book_id=?' ); $db->execute( $sth, array( $this->get_title(), $this->get_author(), $this->get_publisher(), $this->book_id ) ); } function delete() { global $db; $sth = $db->prepare( 'DELETE FROM book WHERE book_id=?' ); $db->execute( $sth, array( $this->book_id ) ); } function delete_all() { global $db; $sth = $db->prepare( 'DELETE FROM book' ); $db->execute( $sth ); }}..
To make this change, you have to do two things. first, you must change the fields from inpidual instance variables to a hash table of field and value pairs. then you must add a _ call method that simply looks at the method name to see whether it was a set _ or a get _ method and set the appropriate field in the hash table.
Note that the load method actually uses the _ call method by calling the set_title, set_author, and set_publisher methods -- none of which actually exists.
Completely dynamic Going completely dynamic
Removing the get _ and set _ methods is just a starting point. to create a completely dynamic database object, you have to give the class the name of the table and the fields, and have no hard-coded references. listing 5 shows this change.
Listing 5. A completely dynamic database object class
getMessage()); }class DBObject{ private $id = 0; private $table; private $fields = array(); function __construct( $table, $fields ) { $this->table = $table; foreach( $fields as $key ) $this->fields[ $key ] = null; } function __call( $method, $args ) { if ( preg_match( "/set_(.*)/", $method, $found ) ) { if ( array_key_exists( $found[1], $this->fields ) ) { $this->fields[ $found[1] ] = $args[0]; return true; } } else if ( preg_match( "/get_(.*)/", $method, $found ) ) { if ( array_key_exists( $found[1], $this->fields ) ) { return $this->fields[ $found[1] ]; } } return false; } function load( $id ) { global $db; $res = $db->query( "SELECT * FROM ".$this->table." WHERE ". $this->table."_id=?", array( $id ) ); $res->fetchInto( $row, DB_FETCHMODE_ASSOC ); $this->id = $id; foreach( array_keys( $row ) as $key ) $this->fields[ $key ] = $row[ $key ]; } function insert() { global $db; $fields = $this->table."_id, "; $fields .= join( ", ", array_keys( $this->fields ) ); $inspoints = array( "0" ); foreach( array_keys( $this->fields ) as $field ) $inspoints []= "?"; $inspt = join( ", ", $inspoints );$sql = "INSERT INTO ".$this->table." ( $fields ) VALUES ( $inspt )"; $values = array(); foreach( array_keys( $this->fields ) as $field ) $values []= $this->fields[ $field ]; $sth = $db->prepare( $sql ); $db->execute( $sth, $values ); $res = $db->query( "SELECT last_insert_id()" ); $res->fetchInto( $row ); $this->id = $row[0]; return $row[0]; } function update() { global $db; $sets = array(); $values = array(); foreach( array_keys( $this->fields ) as $field ) { $sets []= $field.'=?'; $values []= $this->fields[ $field ]; } $set = join( ", ", $sets ); $values []= $this->id;$sql = 'UPDATE '.$this->table.' SET '.$set. ' WHERE '.$this->table.'_id=?'; $sth = $db->prepare( $sql ); $db->execute( $sth, $values ); } function delete() { global $db; $sth = $db->prepare( 'DELETE FROM '.$this->table.' WHERE '. $this->table.'_id=?' ); $db->execute( $sth, array( $this->id ) ); } function delete_all() { global $db; $sth = $db->prepare( 'DELETE FROM '.$this->table ); $db->execute( $sth ); }}$book = new DBObject( 'book', array( 'author', 'title', 'publisher' ) );$book->delete_all();$book->set_title( "PHP Hacks" );$book->set_author( "Jack Herrington" );$book->set_publisher( "O'Reilly" );$id = $book->insert();echo ( "New book id = $id\n" );$book->set_title( "Podcasting Hacks" );$book->update();$book2 = new DBObject( 'book', array( 'author', 'title', 'publisher' ) );$book2->load( $id );echo( "Title = ".$book2->get_title()."\n" );$book2->delete( );? >
Here, you change the name of the class from Book to DBObject. then you change the constructor to take the name of the table, as well as the names of the fields in the table. after that, most of the changes happen in the methods of the class, which instead of using some hard-coded Structured Query Language (SQL) now must create the SQL strings on the fly using the table and the field names.
The only assumptions the code makes is that there is a single primary key field and that the name of that field is the name of the table plus _ id. so, in the case of the book table, there is a primary key field called book_id. the primary key naming standards you use may be different; if so, you will need to change the code to suit.
This class is much more complex than the original Book class. however, from the perspective of the client of the class, this class is still simple to use. that said, I think the class cocould be even simpler. in particle, I don't like that I have to specify the name of the table and the fields each time I create a book. if I were to copy and paste this code all around, then change the field structure of the book table, I wocould be in a bad way. in Listing 6, I solved this problem by creating a simple Book class that inherits from DBObject.
Listing 6. The new Book class
..class Book extends DBObject{ function __construct() { parent::__construct( 'book', array( 'author', 'title', 'publisher' ) ); }}$book = new Book( );$book->delete_all();$book->{'title'} = "PHP Hacks";$book->{'author'} = "Jack Herrington";$book->{'publisher'} = "O'Reilly";$id = $book->insert();echo ( "New book id = $id\n" );$book->{'title'} = "Podcasting Hacks";$book->update();$book2 = new Book( );$book2->load( $id );echo( "Title = ".$book2->{'title'}."\n" );$book2->delete( );?>
Now, the Book class really is simple. And the client of the Book class no longer needs to know the names of the table or the fields.
Room for improvement
One final improvement I want to make on this dynamic class is to use member variables to access the fields, instead of the clunky get _ and set _ operators. listing 7 shows how to use the _ get and _ set magic methods instead of _ call.
Listing 7. Using the _ get and _ set methods
getMessage()); }class DBObject{ private $id = 0; private $table; private $fields = array(); function __construct( $table, $fields ) { $this->table = $table; foreach( $fields as $key ) $this->fields[ $key ] = null; } function __get( $key ) { return $this->fields[ $key ]; } function __set( $key, $value ) { if ( array_key_exists( $key, $this->fields ) ) { $this->fields[ $key ] = $value; return true; } return false; } function load( $id ) { global $db; $res = $db->query( "SELECT * FROM ".$this->table." WHERE ". $this->table."_id=?", array( $id ) ); $res->fetchInto( $row, DB_FETCHMODE_ASSOC ); $this->id = $id; foreach( array_keys( $row ) as $key ) $this->fields[ $key ] = $row[ $key ]; } function insert() { global $db; $fields = $this->table."_id, "; $fields .= join( ", ", array_keys( $this->fields ) ); $inspoints = array( "0" ); foreach( array_keys( $this->fields ) as $field ) $inspoints []= "?"; $inspt = join( ", ", $inspoints );$sql = "INSERT INTO ".$this->table. " ( $fields ) VALUES ( $inspt )"; $values = array(); foreach( array_keys( $this->fields ) as $field ) $values []= $this->fields[ $field ]; $sth = $db->prepare( $sql ); $db->execute( $sth, $values ); $res = $db->query( "SELECT last_insert_id()" ); $res->fetchInto( $row ); $this->id = $row[0]; return $row[0]; } function update() { global $db; $sets = array(); $values = array(); foreach( array_keys( $this->fields ) as $field ) { $sets []= $field.'=?'; $values []= $this->fields[ $field ]; } $set = join( ", ", $sets ); $values []= $this->id;$sql = 'UPDATE '.$this->table.' SET '.$set. ' WHERE '.$this->table.'_id=?'; $sth = $db->prepare( $sql ); $db->execute( $sth, $values ); } function delete() { global $db; $sth = $db->prepare('DELETE FROM '.$this->table.' WHERE '.$this->table.'_id=?' ); $db->execute( $sth, array( $this->id ) ); } function delete_all() { global $db; $sth = $db->prepare( 'DELETE FROM '.$this->table ); $db->execute( $sth ); }}class Book extends DBObject{ function __construct() { parent::__construct( 'book', array( 'author', 'title', 'publisher' ) ); }}$book = new Book( );$book->delete_all();$book->{'title'} = "PHP Hacks";$book->{'author'} = "Jack Herrington";$book->{'publisher'} = "O'Reilly";$id = $book->insert();echo ( "New book id = $id\n" );$book->{'title'} = "Podcasting Hacks";$book->update();$book2 = new Book( );$book2->load( $id );echo( "Title = ".$book2->{'title'}."\n" );$book2->delete( );?>
The test code at the bottom extends strates just how much cleaner this syntax is. to get the title of the book, simply get the title member variable. that variable, in turn, callthe _ get method on the object that looks for the title item in the hash table and returns it.
And there you have it: a single dynamic database access class that can bend itself to fit any table in your database.
By iefreer