IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Open source  >

PHP V5 マイグレーション・ガイド

PHP V4 アプリケーションで V5 のオブジェクト指向機能を活用する

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 中級

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

2006年 9月 26日

PHP V5 の新しい言語機能を使用すると、コードの保守容易性と安定性を大幅に向上させることができます。このような新機能を利用しながら、PHPV4 で開発したコードを V5 に移行する方法について学びます。

PHP V5 は V4 から飛躍的に前進しています。新しい言語機能により、信頼性が高くて保守をしやすいクラス・ライブラリーを、はるかに容易にビルドすることができます。そのうえ、標準ライブラリーを再作成することにより、PHPは Java™ プログラミング言語のような Web 言語にさらに近づいています。PHPの新しいオブジェクト指向機能をいくつか眺めながら、既存の PHP V4 コードをV5 に移行する方法を学びます。

まず、新しい言語機能の概要と、PHP のクリエーターが PHP V4 からオブジェクトの扱いをどのように変えていったのかを見ていきます。V5作成の目的は、Web アプリケーション開発用の業界向けの強力な言語を作成することでした。このことは、PHPV4 の制限を認識した上で、他の言語 (Java、C#、C++、Ruby、Perl などの言語)から既知の優れた言語構造を選び、それを PHP に取り入れることを意味していました。

最初の、しかも最も重要な追加内容は、クラスのメソッド変数とインスタンス変数のアクセス保護、つまりpublic、protected、そして private というキーワードでした。この追加により、クラス設計者はクラスの内部をこれまでどおりコントロールしながら、クラスのクライアントがアクセスできるもの、アクセスできないものを指定できるようになります。

PHP V4 ではすべてが公開されていました。PHP V5 では、クラス設計者は、外部から見えるもの(public) とクラスの内部 (private) またはクラスの子孫の内部 (protected)からしか見えないものを指定できます。これらのアクセス・コントロールなしでは、大規模なグループのコードやライブラリーとして配布されるコードをうまく処理できませんでした。これらのクラスのコンシューマーが間違ったメソッドを使用したり、非公開(private) のメンバー変数であるべき変数にアクセスしてしまう可能性が高かったためです。

もう 1 つの重要な追加は、契約プログラミングを可能にする interface キーワードとabstract キーワードでした。契約プログラミングとは、あるクラスが別のクラスに契約を提供するということです。「私がやるのはこういうことですが、あなたはやり方について知る必要はありません」とも言い換えられます。このインターフェースを実装するすべてのクラスは契約に同意します。インターフェースのすべてのコンシューマーは、インターフェースに指定されたメソッドのみを使用することに同意します。後で見てもらうように、abstractキーワードによってインターフェースの使用はずっと簡単になります。

アクセス・コントロールと契約プログラミングという 2 つの主要な機能を活用すると、以前より大編成のコーダー・チームがスムーズに多量のコード・ベースに取り組めるようになります。また、IDEで、より豊富な言語インテリジェンス機能を提供できるようになります。この記事で取り上げるのは移行に関する問題ですが、これらの新しい重要な言語機能の使用法を紹介することにもかなり時間をかけています。

アクセス・コントロール

この新しい言語機能のデモのため、Configuration というクラスを使用します。この単純なクラスは、あるWeb アプリケーション用の構成可能なアイテム (images ディレクトリのパスなど)を保持します。この情報はファイルまたはデータベースに置くのが理想です。リスト1 は簡易バージョンです。


リスト 1. access.php4
                
<?php
class Configuration
{
  var $_items = array();

  function Configuration() {
    $this->_items[ 'imgpath' ] = 'images';
  }
  function get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->get( 'imgpath' )."\n" );
?>


これは完全に正当な PHP V4 クラスです。あるメンバー変数が構成アイテムのリストを保持し、コンストラクターでこのアイテムをロードします。get()というアクセサー・メソッドはアイテムの値を返します。

スクリプトを実行すると、次のコードがコマンド・ラインに表示されます。

% php access.php4
images
%

うまくいきました。コードは適切に機能しており、imgpath 構成アイテムの値が設定されて正しく読み込まれています。

このクラスを PHP V5 に変換する最初の手順は、コンストラクター名の変更です。V5では、オブジェクトを初期化するメソッド (コンストラクター) には __constructという名前をつけます。このちょっとした変更を次に示します。


リスト 2. access1.php5
                
<?php
class Configuration
{
  var $_items = array();

  function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }
  function get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->get( 'imgpath' )."\n" );
?>

この変更はあまり大きなものではありません。PHP V5 の決まりへと移行しているだけです。次の手順は、クラスのクライアントが$_items メンバー変数を直接読み書きできないように、クラスにアクセス・コントロールを追加することです。この変更を次に示します。


リスト 3. access2.php5
                
<?php
class Configuration
{
  private $_items = array();

  public function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->get( 'imgpath' )."\n" );
?>

このオブジェクトのクライアントがアイテムの配列に直接アクセスしようとしても、配列はprivate になっているのでアクセスは拒否されます。運がよければ、クライアントはget() メソッドを使って必要とされている読み取りアクセスができることに気がつきます。

protected へのアクセスの使い方を紹介するため、Configuration クラスを継承する2 番目のクラスを用意します。その DBConfiguration クラスを呼び出し、あえてデータベースから構成値を読み込もうとします。このセットアップを次に示します。


リスト 4. access3.php
                
<?php
class Configuration
{
  protected $_items = array();

  public function __construct() {
    $this->load();
  }
  protected function load() { }
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  protected function load() {
    $this->_items[ 'imgpath' ] = 'images';
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."\n" );
?>

このリストは protected キーワードの正しい使い方を示しています。基本クラスはload() というメソッドを定義しています。このクラスの子孫は load() メソッドをオーバーライドして、アイテムのテーブルにデータを追加します。load()メソッドはクラスおよびクラスの子孫にとって内部にあるので、外部クライアントからは見えません。キーワードがprivate だとすると、オーバーライドできません。

とはいえ、この設計は完全に満足のいくものではありません。アイテムの配列をDBConfiguration クラスからアクセスできるようにする必要があったからです。むしろConfiguration クラスでアイテムの配列を完全に保守し続けられる方が便利です。そうすれば、他の子孫を追加した場合、これらのクラスがアイテムの配列の保守方法を知る必要がなくなります。その変更を次に行います。


リスト 5. access4.php5
                
<?php
class Configuration
{
  private $_items = array();

  public function __construct() {
    $this->load();
  }
  protected function load() { }
  protected function add( $key, $value ) {
    $this->_items[ $key ] = $value;
  }
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  protected function load() {
    $this->add( 'imgpath', 'images' );
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."\n" );
?>

これで、子孫クラスが protected の add() メソッドを使ってリストに構成アイテムを追加できるので、アイテムの配列をprivate にできます。Configuration クラスでは、子孫クラスについて心配せずに構成アイテムの保存方法や読み取り方法を変更できます。load()メソッドと add() メソッドが同じように機能する限り、子孫には何の問題もありません。

私にとって、アクセス・コントロールの追加は PHP V5 への移行を検討する第一の理由です。GradyBooch がオブジェクト指向の 4 本柱の 1 つとして挙げているからでしょうか。そうではありません。すべてのメソッドとメンバーがpublic で定義されている 10 万行の C++ コードの保守を経験したことがあったからです。私は3 日間かけてコードをクリーンアップし、そのプロセスで、バグの数が大幅に減少し、保守容易性が向上しました。その理由は、アクセス・コントロールなしでは、オブジェクトがどのように相互利用されているかを理解することが不可能だったからです。それに、他の何を壊すことになるかを知らずに変更するのは無謀です。C++の場合は、少なくともコンパイラーがありました。PHP にはコンパイラーがないため、このようなアクセス・コントロールはいっそう重要になります。





上に戻る


契約プログラミング

PHP V4 から V5 への移行時に利用できる次に重要な機能は、インターフェースおよび抽象クラスと抽象メソッドによる契約プログラミングのサポートです。リスト6 は、PHP V4 コーダーが interface キーワードもなしに初歩的なインターフェースを構築しようとしたConfiguration クラスのバージョンを示しています。


リスト 6. interface.php4
                
<?php
class IConfiguration
{
  function get( $key ) { }
}

class Configuration extends IConfiguration
{
  var $_items = array();

  function Configuration() {
    $this->load();
  }
  function load() { }
  function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  function load() {
    $this->_items[ 'imgpath' ] = 'images';
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."\n" );
?>

このリストは IConfiguration クラスで始まり、すべての Configuration クラスまたは派生クラスで提供されるインターフェースを定義しています。このインターフェースは、クラスとそのすべてのクライアントとの間の契約を定義します。この契約では、IConfigurationを実装するすべてのクラスが get() メソッドを持ち、IConfiguration のすべてのクライアントがそのget() メソッドだけを使用する必要があることが指定されています。

このコードは PHP V5 でも機能しますが、次のようにシステムに用意されているinterface を使用する方が適切です。


リスト 7. interface1.php5
                
<?php
interface IConfiguration
{
  function get( $key );
}

class Configuration implements IConfiguration
{
  ...
}

class DBConfiguration extends Configuration
{
  ...
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."\n" );
?>

まず、何が行われているのかが読み手に明確になります。さらに、1 つのクラスで複数のインターフェースを実装できます。リスト8 は、Configuration クラスを拡張して PHP 内部の Iterator インターフェースを実装する方法を示しています。


リスト 8. interface2.php5
                
<?php
interface IConfiguration {
  ...
}

class Configuration implements IConfiguration, Iterator
{
  private $_items = array();

  public function __construct() {
    $this->load();
  }
  protected function load() { }
  protected function add( $key, $value ) {
    $this->_items[ $key ] = $value;
  }
  public function get( $key ) {
    return $this->_items[ $key ];
  }

  public function rewind() { reset($this->_items); }
  public function current() { return current($this->_items); }
  public function key() { return key($this->_items); }
  public function next() { return next($this->_items); }
  public function valid() { return ( $this->current() !== false ); }
}

class DBConfiguration extends Configuration {
  ...
}

$c = new DBConfiguration();
foreach( $c as $k => $v ) { echo( $k." = ".$v."\n" ); }
?>

Iterator インターフェースにより、クライアントに対してすべてのクラスを配列のように見せることができます。スクリプトの最後からわかるように、foreach演算子を使用して、Configuration オブジェクトの構成アイテムをすべて繰り返し処理できます。この機能はPHP V4 では無理でしたが、アプリケーションでは様々な方法で使うことができます。

このインターフェース・メカニズムの利点は、メソッドを実装しなくてもすぐに契約を組み立てられることです。欠点は、インターフェースを実装するために、指定のメソッドをすべて実装する必要があることです。PHPV5 で役立つもう 1 つの追加は「抽象クラス」です。抽象クラスを使うと、1 つの基本クラスからインターフェースのコアを実装し、そこから容易に具象クラスを作成できます。

抽象クラスのもう 1 つの使用法は、基本クラスがインスタンス化されることのない複数の派生クラス用に1 つの基本クラスを作成することです。たとえば、DBConfiguration と Configurationの両方が存在する場合、DBConfiguration のみを使用します。Configuration クラスは単なる基本クラス、つまり抽象クラスです。このように、動作を強制するために次のようにabstract キーワードを使用できます。


リスト 9. abstract.php5
                
<?php
abstract class Configuration
{
  protected $_items = array();

  public function __construct() {
    $this->load();
  }
  abstract protected function load();
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  protected function load() {
    $this->_items[ 'imgpath' ] = 'images';
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."\n" );
?>

これで、Configuration 型のオブジェクトをインスタンス化しようとしても、クラスは抽象的で不完全と見なされるのでエラーになります。





上に戻る


静的メソッドと静的メンバー

PHP V5 のもう 1 つの重要な追加機能は、クラスの静的なメンバーとメソッドのサポートです。この機能により、一般的なシングルトン・パターンを使うことができます。アプリケーションには1 つの構成オブジェクトしかないため、シングルトン・パターンは Configurationクラスにとって理想的です。

リスト 10 は、シングルトンとしての PHP V5 バージョンの Configuration クラスを示しています。


リスト 10. static.php5
                
<?php
class Configuration
{
  private $_items = array();

  static private $_instance = null;
  static public function get() {
    if ( self::$_instance == null ) 
       self::$_instance = new Configuration();
    return self::$_instance;
  }

  private function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }
  public function __get( $key ) {
    return $this->_items[ $key ];
  }
}

echo( Configuration::get()->{ 'imgpath' }."\n" );
?>

static キーワードには多くの使い方があります。単一型のすべてのオブジェクト用のグローバル・データにアクセスする必要がある場合は、検討してみてください。





上に戻る


マジック・メソッド

PHP V5 のもう 1 つの大きな追加はマジック・メソッドのサポートです。マジック・メソッドを使用すると、オブジェクトのインターフェースをその場で変更できます。たとえば、Configurationオブジェクトの構成アイテムごとにメンバー変数を追加できます。get() メソッドを使うのではなく、次に示すように、特定のアイテムを配列であるかのようにそのまま要求します。


リスト 11. magic.php5
                
<?php
class Configuration
{
  private $_items = array();

  function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }
    function __get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->{ 'imgpath' }."\n" );
?>

ここでは、クライアントがオブジェクトでメンバー変数を探すたびに呼び出される新しい__get() メソッドを作成しています。メソッド内のコードは次にアイテムの配列を使用して値を検索し、特にそのキーワードに対してメンバー変数が存在していたかのようにその値を返します。スクリプトの一番下を見ると、Configurationオブジェクトの使い方がとてもすっきりしていて、オブジェクトが配列であるかのようにimgpath の値を要求しているのがわかります。

PHP V4 から V5 への移行時には、V4 ではまったく利用できなかったこのような言語機能に注目し、クラスをもう一度調べてその機能を使用する方法を検討することが重要です。





上に戻る


例外

この記事の締めくくりとして、PHP V5 の新しい例外メカニズムを見ていきます。例外は、エラー処理について検討するためのまったく新しい方法を提供します。あらゆるプログラムでは、ファイルが見つからない、メモリが足りないなどのエラーが必然的に発生します。例外がないと、エラー・コードに戻る必要がありました。次のPHP V4 コードを見てください。


リスト 12. file.php4
                
<?php
function parseLine( $l )
{
   // ...
   return array( 'error' => 0,
     data => array() // data here
   );
}

function readConfig( $path )
{
  if ( $path == null ) return -1;
  $fh = fopen( $path, 'r' );
  if ( $fh == null ) return -2;

  while( !feof( $fh ) ) {
    $l = fgets( $fh );
    $ec = parseLine( $l );
        if ( $ec['error'] != 0 ) return $ec['error'];
  }

  fclose( $fh );
  return 0;
}

$e = readConfig( 'myconfig.txt' );
if ( $e != 0 )
  echo( "There was an error (".$e.")\n" );
?>

この標準ファイル I/O コードは、ファイルを読み取ってあるデータを取得し、問題が発生すると、エラー・コードを返します。このスクリプトでは2 つの問題があります。最初の問題はエラー・コードです。エラー・コードは何を意味しているのでしょうか。これを理解するには、エラー・コードを意味のある文字列にマッピングする2 番目のシステムを作成する必要があります。2 番目の問題は、parseLine からの戻りが複雑になることです。単純にデータを返したいだけなのに、実際にはエラー・コードとデータを返す必要があります。エンジニアとは(私も含めて) ものぐさになることがあまりにも多く、データだけを返して、管理が面倒なのでエラーは無視しがちです。

リスト 13 は、例外を使うとコードがいかにすっきりするかを示しています。


リスト 13. file.php5
                
<?php
function parseLine( $l )
{
   // Parses and throws and exception when invalid
   return array(); // data
}

function readConfig( $path )
{
  if ( $path == null )
    throw new Exception( 'bad argument' );

  $fh = fopen( $path, 'r' );
  if ( $fh == null )
    throw new Exception( 'could not open file' );

  while( !feof( $fh ) ) {
    $l = fgets( $fh );
    $ec = parseLine( $l );
  }

  fclose( $fh );
}

try { 
  readConfig( 'myconfig.txt' );
} catch( Exception $e ) {
  echo( $e );
}
?>

例外はその中に問題の説明について説明したテキストが含まれるため、エラー・コードについて心配する必要はありません。また、問題が発生すると、その関数は単純にエラーをスローするので、parseLineからの戻り値に含まれるエラー・コードへの対応を心配する必要もありません。スタックは、スクリプトの一番下にある、最も近いtry/catch ブロックまで戻ります。

例外によって、コードの作成方法は一変します。やっかいなエラー・コードやマッピングを管理する必要がなくなり、当面の問題に集中できるようになります。また、コードは読みやすく、保守しやすいものになり、エラー処理を追加する気になるかもしれません。エラー処理には必ず利点があるものです。





上に戻る


まとめ

新しいオブジェクト指向機能と例外処理の追加は、PHP V4 から V5 にコードを移行するための魅力的な理由となります。ご覧のように、アップグレードはそれほど難しくありません。PHPV5 の拡張機能の構文は PHP らしいものです。確かに、拡張機能は Ruby のような言語から来ていますが、うまく適合されていると思います。そして、PHPの守備範囲は、小さなサイト用のスクリプト言語から、エンタープライズ・レベルで競争できるものへと拡張されています。



参考文献

学ぶために

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


議論するために
  • Grady Booch blog について developerWorks Grady Booch blog でさらに学んでください。

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


著者について

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




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ