PHP語言的動態特性-Going dynamic with PHP

來源:互聯網
上載者:User
文章目錄
  • 動態特性的重要性The importance of being dynamic
  • 寫一個柔性類Writing a bendy class
  • 傳統的資料庫訪問方式Classic database access
  • 引入一點動態A little dab of dynamic
  • 完全動態化Going completely dynamic
  • 還可以改進的地方Room for improvement

原文連結:

http://www.ibm.com/developerworks/xml/library/os-php-flexobj/

PHP5引入的物件導向的編程特性顯著的提升了PHP語言的層次。不只是成員和方法的存取控制private, protected, public -- 和Java, C++, 或C#一樣 -- 你同時還能建立運行期間能動態改變的對象,動態建立一個新的方法和屬性。這些是Java, C++, 或C#語言無法提供的。這種語言能力使得類似於ROR這樣的快速開發架構變得可能。

在進入正題之前,先提個醒:這篇文章涉及PHP5裡面很進階的OOP特性使用,而這些特性並非在所有的應用程式中都會使用。而且,這些特性比較難以理解,如果你對OOP沒有深刻理解的話,至少你得初步瞭解PHP對象的文法。

動態特性的重要性The importance of being dynamic

對象是雙刃劍。一方面,對象是很好的封裝資料和邏輯的方法,建立系統更直觀且更易於維護。另一方面,它也可能會引起類膨脹,代碼變得瑣碎,需要編寫大量重複性的代碼,你只能希望中間不犯錯誤。這個問題的一個典型例子就是資料庫訪問和操作的對象。一般而言,你需要為每一張表建立一個簡單的類,提供CRUD等方法。但也有另外的方法,建立一個Null 物件,設定屬性,然後插入資料庫。

比如你有一張表Customers, 你得建立一個Customer對象,擁有表中欄位所對應的屬性,代表單個客戶。這個Customer對象允許你insert, update或者delete相應的資料庫記錄。這看來不錯,能工作,但有一堆代碼要寫。如果你有20張表,那麼你需要寫20個類。

有三個方法來解決這個問題。第一個方法copy/paste/modify,對於小工程,這沒什麼。第二個方法是使用代碼產生器讀取資料庫方案然後自動為你產生代碼,很多開發架構提供代碼產生器,這是個好主意。第三個方法,也就是本文所述,是通過編寫一個簡單的類,在運行時根據給定表的欄位動態模型化。這個類比之前的特定表格的模型類效能上要差點,但是代碼精簡了很多。這個方法對於設計經常變動的項目特別合算。

那麼,怎麼寫一個柔韌的類呢?

寫一個柔性類Writing a bendy class

對象擁有兩個方面:成員變數和方法。對於傳統的編譯語言,如Java,如果你想調用一個不存在的方法或者一個不存在的成員變數,會報編譯錯誤。對於PHP,情況如何呢?

PHP的一個方法調用,工作方式如下。首先,PHP解譯器查看該類的方法,如果方法存在,那麼直接調用。否則,調用該類的魔法函數__call如果該方法存在的話,如果調用失敗,則調用父類的方法,以此類推。

魔法函數是特定命名的方法,這個方法由PHP解譯器在指令碼執行的特定節點所觸發。

最常見的魔法函數是對象建立時調用的建構函式。

__call方法有兩個參數:要求方法名和參數。通過__call機制實現和自己寫一個方法,對函數調用者而言,效果是一樣的。這樣,你可以建立一個包含"無數"方法的對象。

除了__call方法, 其他魔法函數,如__get 和 __set, 是在引用不存在的執行個體變數的時候被調用。記住這一點,你可以開始寫適用於任何資料庫表訪問的類了。

(以下主要是代碼重構,保持原文)

傳統的資料庫訪問方式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

<?phprequire_once("DB.php");$dsn = 'mysql://root:password@localhost/bookdb';$db =& DB::Connect( $dsn, array() );if (PEAR::isError($db)) { die($db->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 included.

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

引入一點動態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 individual fields. Listing 4 shows the updated code.

Listing 4. Dynamic get_ and set_ methods

<?phprequire_once("DB.php");$dsn = 'mysql://root:password@localhost/bookdb';$db =& DB::Connect( $dsn, array() );if (PEAR::isError($db)) { die($db->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 individual 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.

完全動態化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

<?phprequire_once("DB.php");$dsn = 'mysql://root:password@localhost/bookdb';$db =& DB::Connect( $dsn, array() );if (PEAR::isError($db)) { die($db->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 could be even simpler. In particular, 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 would 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

<?phprequire_once("DB.php");$dsn = 'mysql://root:password@localhost/bookdb';$db =& DB::Connect( $dsn, array() );if (PEAR::isError($db)) { die($db->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 illustrates just how much cleaner this syntax is. To get the title of the book, simply get the title member variable. That variable, in turn, calls the __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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.