PHP V5.3 は、遅延静的バインディング (LSB) 機能を通じてオブジェクト指向プログラミング (OOP) のいくつかの問題を解決します。 LSB が PHP の OOP プログラミングの問題をどのように解決するか、また LSB の使用を必要とするよく知られたオブジェクト指向設計パターンを実装する方法を学びます。
オブジェクト指向プログラミング (OOP) を使用すると、開発者は、OOP を深く理解していることを前提として、データの抽象化、カプセル化、モジュール性、ポリモーフィズム、継承を使用してコードを削減および簡素化できます。 OOP 機能の知識があれば、PHP コーダーはデザイン パターン (一般的な問題を解決するためのよく知られたアルゴリズム) を活用することもできます。 PHP は V3.0 から OOP 機能を提供してきましたが、V5.3 が登場するまでは、PHP の OOP 実装内の不具合により、いくつかの一般的なデザイン パターンを使用できませんでした。 PHP V5.3 の遅延静的バインディング (LSB) 機能の出現により、これらの癖は完全になくなりました。
この記事では、PHP V5.3 が登場する前に問題のある設計パターンをいくつか紹介し、これらのパターンが機能しない理由を説明します。次に、PHP V5.3 の LSB 機能が実証され、シングルトンおよびアクティブ レコードの設計パターンが示されます。
OOP の再訪
過去に PHP OOP を使用したことがある場合は、次の理由で PHP OOP を使用しないことに決めた可能性が高くなります。
PHP OOP に問題があると主張する多くのブログ投稿の 1 つを読んでください。
単純なデザインパターンを実装しようとしましたが、失敗しました。
PHP V5.3 に関しては、OOP に関するブログ投稿はすべて肯定的なものであり、PHP OOP の問題は大幅に解決されています。 PHP OOP に戻る時が来ました。この記事では、V5.3 以前に問題があったいくつかの設計パターン (シングルトン、ジェネレーター、ファクトリ メソッド、およびアクティビティ レコード) について説明します。
シングルトン、ジェネレーター、およびファクトリ メソッドの設計パターンは、オブジェクトの構築を支援するため、作成パターンとみなされます。シングルトン パターンは、おそらく最も一般的に使用される OOP 設計パターンの 1 つであり、クラスのオブジェクト インスタンスの数を 1 に制限します。たとえば、データベース接続プーリングは、シングルトン設計パターンの一例です。通常、アプリケーションには、リソースを大量に消費する接続プール クラスのインスタンスが複数存在することは望ましくありません。
複雑なオブジェクトの構築とプレゼンテーションを分離する必要がある場合は、ジェネレーター デザイン パターンを使用する必要があります。同じ構築プロセスを使用して複数のオブジェクトを作成できます。ジェネレーター パターンの実装は複雑になる可能性がありますが、ジェネレーターを使用できるようになると、ジェネレーターによって作成されたオブジェクトの構築と使用が簡素化されます。 HTML、XML、または PDF を出力する機能を備えたトランスフォーマーは、ジェネレーターが必要となる例です。
ファクトリ メソッド パターンは、その名前が示すように、多数のオブジェクトを生成するために使用されるメソッドの実装を定義します。ファクトリ メソッド パターンは、アプリケーションがサブクラスの実装に依存するタイプのオブジェクトを作成する必要がある場合に使用できます。
アクティブ レコード パターンを使用して、ドメイン クラス内でリレーショナル データベースの永続化メソッドをラップできます。アクティブなレコードの各インスタンスは、データベース内の特定の行に関連付けられます。このクラスには、データベース内の 1 つ以上の行を挿入、削除、および更新するメソッドが含まれています。 Active Record 設計パターンは、Martin Fowler によって「エンタープライズ アプリケーション アーキテクチャのパターン」で定義され、Ruby on Rails 内での使用がますます普及しています。
元 LSB の創造的なデザイン パターンの実装の問題
上記の 4 つの設計パターンはすべて、静的なプロパティとメソッドを使用します。たとえば、リスト 1 に示す接続プール シングルトンを見てください。
リスト 1. 単純なシングルトン
クラス ConnPool {
プライベート静的 $onlyOne
プライベート静的 $count = 0;
プライベート関数 __construct() {
// 現実世界の db conn に関するものはここにあります...
}
パブリック静的関数 getInstance() {
if (!is_object(self::$onlyOne)) {
$klass = __CLASS__;
self::$onlyOne = 新しい $klass()
自分自身::$count++;
}
self::$onlyOne を返します
}
public static function getInstanceCount() {return self::$count;}
}
$db = ConnPool::getInstance();
アサート (1 == $db->getInstanceCount());
$db2 = ConnPool::getInstance();
アサート (1 == $db2->getInstanceCount());
?>
この静的な $onlyOne 変数に注意してください。この変数は、接続プール オブジェクトのインスタンスを保持するように設計されています。 $onlyOne の前の static 修飾子は、この変数をクラス自体に関連付けます。 $onlyOne 変数は、スコープがこのクラスであるため、クラス プロパティです。また、$onlyOne プロパティにはインスタンスが 1 つだけあります。プロパティに静的修飾子がない場合、そのプロパティはクラスの各インスタンスに一意であるため、オブジェクト プロパティであると言われます。
ConnPool のコンストラクター メソッド (__construct と呼ばれる) が空であることに注意してください。実稼働実装では、このメソッドを使用してデータベース接続プールの間隔を作成できます。
静的な getInstance メソッドには、シングルトンのテンプレート コードが含まれています。静的な $onlyOne 変数が空の場合にのみ、$onlyOne インスタンスが作成されます。 __CLASS__ 変数を使用してクラスの型を取得し、そのクラスのインスタンスを作成する方法に注目してください。
getInstanceCount メソッドは、接続プールのインスタンスが 1 つだけ作成されていることを証明するためにのみ使用します。リスト 1 の下部にある 4 行のコードは、ConnPool プール クラスのインスタンスが何度リクエストされても、同じオブジェクトを返すことを証明しています。
これまでのところ、このシングルトンは正常に動作しています。複数のデータベースをサポートするために、オブジェクト指向の継承ツリーの形式でこの接続プールをサブクラス化することを決定するまでです。リスト 2 は、この継承ツリーを示しています (わかりやすくするためにインスタンス カウンターとコンストラクター コードは削除されています)。
リスト 2. LSB を使用しないシングルトンでの失敗した試行
クラス ConnPool {
プライベート静的 $onlyOne
保護された静的 $klass = __CLASS__
パブリック静的関数 getInstance() {
if (!is_object(self::$onlyOne)) {
self::$onlyOne = 新しい self::$klass();
}
self::$onlyOne を返します
}
}
class ConnPoolAS400 extends ConnPool {
保護された静的 $klass = __CLASS__
}
$db = ConnPoolAS400::getInstance();
assert ('ConnPoolAS400' == get_class($db)); // 失敗します
?>
複数のタイプのシングルトン クラスをサポートするために、ConnPool クラスは $klass 静的変数を追加し、それがサブクラスでオーバーライドされることを前提としています。 ConnPoolAS400 サブクラスは ConnPool クラスを拡張し、独自のバージョンの $klass プロパティを提供します。 ConnPoolAS400 クラスのインスタンスが作成されると、$klass プロパティに ConnPoolAS400 が保持されることが期待されます。しかし、このコードを実行すると、期待どおりに動作しません。 PHP ユーティリティ関数 get_class が ConnPoolAS400 ではなく ConnPool を返す場合、コードの下部の宣言は失敗します。 問題は、ConnPool クラスの getInstance メソッドが、ConnPoolAS400 のオーバーライドされたバージョンではなく、独自のバージョンの $klass プロパティを使用することです。