PHPでダイナミックに

動的オブジェクトを使ってアプリケーションを柔軟にする

PHP V5の新しいオブジェクト指向プログラミング機能は、この言語の機能性レベルを大幅に向上させています。PHP V5の動的機能を利用して、皆さんの必要に応える柔軟なオブジェクトの作り方を学びましょう。

Jack Herrington (jherr@pobox.com), Editor-in-Chief, Code Generation Network

Jack D. Herringtonは、20年以上の経験を持つシニア・ソフトウェア・エンジニアです。著者には、「Code Generation in Action」、「Podcasting Hacks」、そして近々刊行予定の「PHP Hacks」の3冊があります。彼は30本以上の技術記事も執筆しています。



2006年 2月 07日

PHP V5では新しいオブジェクト指向プログラミング機能が導入され、この言語の機能性レベルが大幅に向上されています。今やPHPはJavaやC++、C#などと同じように、プライベート・メンバーやプロテクト・メンバー、パブリック・メンバーなどといった変数やファンクションが持てるだけではなく、実行時に柔軟に変更可能なオブジェクトを作ることができます。つまり新しいメソッドやメンバー変数を、オンザフライで作成できるのです。JavaやC++、C#などの言語では、こんなことはできません。こうした機能性によって、非常に高速なアプリケーション開発システム、つまりRubyやRailsなどが可能となるのです。

そうした機能に入り込む前に、一言注意しておきます。この記事は、PHP V5で採用されている非常に高度なオブジェクト指向プログラミング機能に関するものであり、こうした機能は、どのアプリケーションでも必ず必要なわけではありません。また、オブジェクト指向プログラミングに関する確実な基礎と、PHPオブジェクト構文に関して少なくとも初心者レベルの知識がないと、こうした機能を理解するのは困難だと思います。

動的であることの重要性

オブジェクトは両刃の剣です。オブジェクトは一面で、データやロジックをカプセル化し、維持管理容易なシステムを作る上で、素晴らしい方法と言うことができます。その一方で、オブジェクトは冗長になりがちであり、大量の余分なコードを書くことが要求されるため、間違いを避けるだけで精一杯となる場合も珍しくありません。こうした問題が起きがちな例として、データベース・アクセス・オブジェクトがあります。『オブジェクトがデータベースからデータ行を読み取り、フィールドの更新を許可した後、与えられた新しいデータで、あるいはその行を削除することでデータベースを更新することを許可する』というようなファンクションを実行するデータベース・テーブルの場合、一般的にはデータベース・テーブルそれぞれに対して1つのクラスが必要です。また別な場合として、新しい空オブジェクトを作成し、そのフィールドを設定し、そのデータをデータベースに挿入するような場合もあります。

データベースの中にCustomersという名前のテーブルがあったとすると、そのテーブルからのフィールドを持ち、ある1人の顧客を表現する、Customerという名前のオブジェクトがあるはずです。そしてそのCustomerオブジェクトによって、対応するレコードをデータベースの中に挿入し、更新し、あるいは削除することができます。ここまでは問題なく、充分納得できます。しかし、このためには大量のコードを書かなければなりません。データベースの中に20のテーブルがあるとすると、20のクラスが必要です。

これに対して、3つの解決方法があります。最初の方法は、単純にキーボードの前に座り、しばらくの間タイプし続ける方法です。これは小さなプロジェクトでは問題ありませんが、私は怠け者です。2番目の解決方法は、データベース・スキーマを読み取り、コードを書き出してくれるコード・ジェネレーターを使う方法です。これは素晴らしい考えですが、他の記事で取り上げるべき話題です。3番目の解決方法は、この記事で私が書こうとしている方法ですが、対象とするテーブルのフィールドに合うように、実行時に動的に変身する1つのクラスを書く方法です。このクラスは、テーブル専用のクラスよりも少し実行が遅いかも知れませんが、大量のコードを書く仕事から開発者を解放してくれるのです。この方法は、プロジェクトの開始時に特に有利です。(プロジェクトの開始時には、テーブルやフィールドが常に変更されており、頻繁な変更に追いつくことが致命的に重要なものです)。

では、柔軟に変身するクラスはどのように書くのでしょう。


Jikes 柔軟に変身するクラスを書く

オブジェクトには、『メンバー変数』と『メソッド』という2つの側面があります。Java言語などのコンパイル言語では、存在しないメソッドを呼ぼうとすると、あるいは存在しないメンバー変数を参照しようとすると、コンパイル時エラーとなります。では、コンパイルしない言語、PHPなどでは一体何が起こるのでしょう。

PHPでのメソッド・コールは次のように動作します。まずPHPインタープリターは、そのメソッドをクラス上で探します。もしそのメソッドがあれば、PHPはそれを呼びます。それ以外の場合には、もしそのメソッドが存在するのであれば、そのクラス上でmagicメソッド、__callが呼び出されます。もし__callが失敗すると、その親クラス・メソッドが呼び出され、・・・と続きます。

Magicメソッド

Magicメソッドは、スクリプト実行中のある時点でPHPインタープリターが検索する、特定な名前を持ったメソッドです。最も一般的なmagicメソッドは、オブジェクトが作成される際に呼ばれるコンストラクターです。

__callメソッドには、2つのパラメーター(要求されたメソッドの名前と、その引数)があります。こうした2つの引数を持つ__callメソッドを作成し、あるファンクションを実行してからTRUEを返す場合、そのオブジェクトを呼ぶコードには、コードを持つメソッドと__call機構が処理するメソッドとの違いが分かりません。そのため、あたかも無限な数のメソッドを持つように見えるオブジェクトを、オンザフライで作成できるのです。

__callメソッドの他にも、メンバー・インスタンス変数で呼び出される他のmagicメソッド(__getや__setなどを含む)は、存在しないとして参照されます。これを頭に置いておけば、どんなテーブルにも合わせて柔軟に変身する、動的なデータベース・アクセス・クラスを書き始めることができます。


古典的なデータベース・アクセス

まず、単純なデータベース・スキーマから始めましょう。リスト1は、本のリストを保持するデータベースに対するスキーマです。このデータベースは、1つのデータ・テーブルのみを持っています。

リスト1.  MySQLデータベース・スキーマ
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 )
);

このスキーマを、bookdbという名前のデータベースの中にロードします。

次に、通常のデータベース・クラスを書きます。後で、これを動的なものに変更するのです。リスト2は、本のテーブルに対する単純なデータベース・アクセス・クラスです。

リスト2.  基本的なデータベース・アクセス・クライアント
<?php
require_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( );
?>

ここではコードを単純にするために、このクラスとテスト・コードを1つのファイルの中に入れています。このファイルは、まずデータベース・ハンドルを取得し、グローバル変数に保存します。次に、各フィールドに対してプライベート・メンバー変数を持つBookクラスが定義されます。また行をロードし、挿入し、更新し、そしてデータベースから削除するための一連のメソッドも含まれています。

一番下にあるテスト・コードは、データベースからすべてのエントリーを削除すると起動します。次にこのコードはbookを挿入し、新しいレコードのIDを知らせます。次に、そのbookを別のオブジェクトにロードし、その標題を出力します。

リスト3は、PHPインタープリターのコマンドラインでコードを実行した場合にどうなるかを示しています。

リスト3.  コマンドラインでコードを実行する
% php db1.php
New book id = 25
Title = PHP Hacks
%

あまり大した量はありませんが、全体に関わる要点を含んでいます。Bookオブジェクトは、bookというデータ・テーブルの1行を表現しています。上記のフィールドやメソッドを使用すると、新しい行を作成し、更新し、それらを削除することができます。


少しばかり動的に

次のステップは、個々のフィールドに対するget_メソッドとset_メソッドをオンザフライで作成することによって、このクラスを少しばかり動的にすることです。リスト4は、更新されたコードを示しています。

リスト4.  動的なget_メソッドとset_メソッド
<?php
require_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 );
  }
}

..

この変更を行うためには、2つのことをする必要があります。まずフィールドを、個々のインスタンス変数から、フィールドと値の対によるハッシュ・テーブルへと変更する必要があります。次に、set_メソッドかget_メソッドかを判断するために単純にメソッド名を見る__callメソッドを追加し、ハッシュ・テーブルに適当なフィールドを設定する必要があります。

loadメソッドが、set_titleやset_author、set_publisherなど、現実には存在しないメソッドを呼ぶことによって、実際に__callメソッドを使っていることに注意してください。


完全に動的にする

get_メソッドとset_メソッドを削除することは、単なる出発点にすぎません。完全に動的なデータベース・オブジェクトを作るためには、テーブル名とフィールドをクラスに与え、そのクラスがハードコード化された参照を持たないようにする必要があります。リスト5は、この変更を示しています。

リスト5.  完全に動的なデータベース・オブジェクト・クラス
<?php
require_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( );
? >

ここでは、クラス名をBookからDBObjectに変更します。次にコンストラクターを変更し、テーブル名と、テーブル中のフィールド名を使うようにします。そうすると、大部分の変更はこのクラスのメソッドの中で行われることになります。それによって今度は、ハードコード化されたSQL(Structured Query Language)を使う代わりに、テーブル名とフィールド名を使ってオンザフライでSQLストリングを作らなければなりません。

このコードが前提としている条件は、プライマリー・キー・フィールドは1つであり、そのフィールド名はテーブル名プラス_idである、ということだけです。従ってbookというテーブルの場合であれば、book_idという名前のプライマリー・キー・フィールドがあります。皆さんがプライマリー・キーにどのような名前付け規則を適用するかによって、コードを変更する必要があるかも知れません。

このクラスは、元々のBookクラスよりもずっと複雑です。しかし、このクラスのクライアントの観点から見ると、このクラスの使い方は相変わらず単純です。とは言っても、このクラスはさらに単純化できるようです。特に私としては、bookを作成する度にテーブル名とフィールドを規定する必要があるのが気に入りません。このコードをあちこちにコピー/ペーストしてからbookテーブルのフィールド構造を変更しようとすると、面倒なことになります。リスト6では、DBObjectを継承する単純なBookクラスを作ることによって、この問題を解決しています。

リスト6.  新しいBookクラス
..
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( );
?>

これで、Bookクラスは本当に単純になりました。そしてBookクラスのクライアントは、もうテーブル名やフィールドを知る必要がありません。


改善の余地

最後にもう一つの変更として、面倒なget_演算子やset_演算子の代わりに、メンバー変数を使ってフィールドにアクセスするようにしたいと思います。リスト7は、__callの代わりに__getや__setというmagicメソッドを使う方法を示しています。

リスト7.  __getメソッドと__setメソッドを使う
<?php
require_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( );
?>

一番下にあるテスト・コードを見ると、この構文がいかに簡潔か分かると思います。本の表題を取得するためには、単純にtitleメンバー変数を取得します。そうすると今度は、この変数がオブジェクト上の__getメソッドを呼び、このメソッドはハッシュ・テーブルの中のtitleアイテムを検索し、それを返します。

これで、できあがりました。動的な1つのデータベース・アクセス・クラスが、データベース中の任意のテーブルに合わせて、柔軟に変身するのです。


動的クラスの他の使い方

動的クラスを書くのは、データベース・アクセスのためだけではありません。リスト8のCustomerオブジェクトの場合を考えてみてください。

リスト8.  単純なCustomerオブジェクト
<?php
class Customer
{
  private $name;

  function set_name( $value )
  {
    $this->name = $value;
  }

  function get_name()
  {
    return $this->name;
  }
}

$c1 = new Customer();
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>

このオブジェクトは非常に単純です。しかし、customerの名前が取り出される度に、あるいはcustomerの名前を設定する度にログが必要だとしたらどうでしょう。そうした場合には、このオブジェクトを、あたかもCustomerオブジェクトのように見えながらログへのget操作やset操作の通知を送る、動的なロギング・オブジェクトの中にラップできるのです。リスト9は、こうしたタイプのラッパー・オブジェクトを示しています。

リスト9.  動的なラッパー・オブジェクト
<?php
class Customer
{
  private $name;

  function set_name( $value )
  {
    $this->name = $value;
  }

  function get_name()
  {
    return $this->name;
  }
}

class Logged
{
  private $obj;

  function __call( $method, $args )
  {
    echo( "$method( ".join( ",", $args )." )\n" );
return call_user_func_array(array(&$this->obj,
   $method), $args );
  }

  function __construct( $obj )
  {
    $this->obj = $obj;
  }
}

$c1 = new Logged( new Customer() );
$c1->set_name( "Jack" );
$name = $c1->get_name();
echo( "name = $name\n" );
?>

ログをとられるバージョンのCustomerを呼ぶコードは、前と同じように見えますが、今度はCustomerオブジェクトへのアクセスが全てログされています。リスト10は、この、ログ対象のコードを実行する場合に、ログが出力するものを示しています。

リスト10.  ログ対象のオブジェクトを実行する
% php log2.php
set_name( Jack )
get_name(  )
name = Jack
%

ここでログは、set_nameメソッドがJackというパラメーターで呼ばれたことを出力しています。次にget_nameメソッドが呼ばれています。そして最後に、テスト・コードはget_nameコールの結果を出力しています。


まとめ

こうした動的オブジェクトが理解しにくいと思う人がいても、私は不思議には思いません。私自身も、理解し、効果を確認するまでには、何度も行ったり来たりしながらコードをいじらなければならなかったのです。

動的オブジェクトは強力なものですが、大きな危険も含んでいます。第1に、magicメソッドを書き始めると、途端にクラスが非常に複雑になります。こうしたクラスは、理解やデバッグ、維持管理が難しいものです。さらに、統合開発環境(IDE: integrated development environment)がインテリジェントになるにつれ、ここで説明したような動的クラスで問題が起きる可能性があります。クラス上のメソッドを参照しようとしても、メソッドを見つけることができないからです。

だからと言って、こうしたタイプのコードを書くことを避けるべきではありません。むしろ全く逆なのです。PHPの設計者達が非常に考え深かったおかげで、ここに挙げたようなmagicメソッドが言語の中に含まれることになり、正にこうしたタイプのコードを書けるようになったのです。しかし、利点を理解するのと同時に、欠点もよく理解しておくことが重要です。

そしてデータベース・アクセスのようなアプリケーションでは、ここで示したような手法、つまり非常に一般的なRubyやRailsシステムの中で使われている手法と似た手法を使うことによって、PHPでデータベース・アプリケーションを実装するために必要な時間を劇的に短縮できるのです。そして、時間を節約することは、何ら悪いことではないはずです。

参考文献

学ぶために

  • PHPに関するすべてにとって、PHP.netは出発点です。
  • Object Oriented PHPは、PHPのオブジェクト指向機能を学ぶための場所として好適です。
  • Magic Methodsに関するドキュメンテーションを調べてみてください。
  • Matt Zandstra著によるPHP 5 Objects, Patterns, and Practice(2004年Apress刊)は、PHP V5オブジェクトを的確に解説した本です。
  • オブジェクトやパターンに関する古典的な本として、Erich GammaとRichard Helm、Ralph Johnson、John Vlissidesの共著による Design Patterns: Elements of Reusable Object-Oriented Softwareがあります(1995年Addison-Wesley Professional刊)。オブジェクトを学ぼうとするプログラマーにとっては必読です。
  • Ruby言語に関する情報を入手してください。
  • Ruby on Railsパッケージを見ると、これらの概念の実際を理解できるでしょう。
  • PHPについて学ぶために、developerWorksのPHP project resourcesを見てください。
  • PHPによるプログラミングを学ぶためのdeveloperWorksのチュートリアル・シリーズとして、「Learning PHP, Part 1」、「Part 2」、「Part 3」があります。
  • developerWorksのOpen sourceゾーンをご覧ください。オープンソース技術を使った開発や、IBM製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。

製品や技術を入手するために

  • 皆さんの次期オープンソース開発プロジェクトを、IBM trial softwareを使って構築してください。ダウンロードで、あるいはDVDで入手することができます。

議論するために

  • developerWorks blogsに参加して、developerWorksのコミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=237338
ArticleTitle=PHPでダイナミックに
publish-date=02072006