PHPプロジェクトでは、ヘルパーオブジェクトを格納、アクセス、および編成するためにどのようなパターンが存在しますか?[閉まっている]


114

PHPベースのオブジェクト指向プロジェクトで、データベースエンジン、ユーザー通知、エラー処理などのヘルパーオブジェクトをどのように整理して管理しますか?

大規模なPHP CMSがあるとします。CMSはさまざまなクラスで構成されています。いくつかの例:

  • データベースオブジェクト
  • ユーザー管理
  • アイテムを作成/変更/削除するためのAPI
  • エンドユーザーにメッセージを表示するメッセージングオブジェクト
  • 正しいページに移動するコンテキストハンドラ
  • ボタンを表示するナビゲーションバークラス
  • ロギングオブジェクト
  • おそらく、カスタムエラー処理

私は、これらのオブジェクトを必要とするシステムの各部分がこれらのオブジェクトにアクセスしやすくするための永遠の質問に取り組んでいます。

何年も前の私の最初のアプローチは、これらのクラスの初期化されたインスタンスを含む$ applicationグローバルを持つことでした。

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

次に、シングルトンパターンとファクトリー関数に切り替えました。

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

しかし、私もそのことに満足していません。ユニットテストとカプセル化は私にとってますます重要になり、私の理解では、グローバル/シングルトンの背後にあるロジックはOOPの基本的な考え方を破壊します。

それからもちろん、各オブジェクトに必要なヘルパーオブジェクトへのポインターの数を与える可能性があります。おそらく最もクリーンで、リソースを節約し、テストしやすい方法ですが、長期的にはこれの保守性に疑問があります。

私が調べたほとんどのPHPフレームワークは、シングルトンパターン、または初期化されたオブジェクトにアクセスする関数を使用しています。どちらもすばらしいアプローチですが、先ほど述べたように、どちらにも満足しています。

ここにどのような一般的なパターンが存在するかについて、視野を広げたいと思います。私はからこれを議論するリソースへの例、追加のアイデアやポインタを探しています長期的現実世界の視点。

また、この問題に対する専門的でニッチなまたは奇妙なアプローチについても興味を持っています。


1
私は非常によく似た質問をしましたが、これにも恵みがありました。あなたはそこにいくつかの答えを理解し得る:stackoverflow.com/questions/1967548/...
philfreo

3
頭を上げて、参照によって新しいオブジェクトを返す- $mh=&factory("messageHandler");は無意味であり、パフォーマンス上の利点はありません。また、これは5.3で廃止されました。
ryeguy

回答:


68

Flaviusによって提案されたシングルトンアプローチは避けます。このアプローチを回避する理由は数多くあります。これは適切なOOP原則に違反しています。グーグルテストブログには、シングルトンとその回避方法に関する優れた記事がいくつかあります。

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

代替案

  1. サービスプロバイダー

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. 依存性注入

    http://en.wikipedia.org/wiki/Dependency_injection

    そしてphpの説明:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

これはこれらの代替案についての良い記事です:

http://martinfowler.com/articles/injection.html

依存性注入(DI)の実装:

フラビウスの解決策についてさらに考えてみましょう。この投稿が反投稿になりたくないのですが、依存関係の注入が少なくとも私にとってグローバルよりも優れている理由を理解することが重要だと思います。

「真の」シングルトン実装ではありませんが、フラビウスはそれを間違っていたと思います。グローバルな状態が悪いです。このようなソリューションでは、静的メソッドのテストが難しいことにも注意してください。

多くの人がそれを行い、承認し、使用することを知っています。しかし、Misko Heverysのブログ記事(グーグルのテスト容易性の専門家)を読んで、それを読み直し、彼の言うことをゆっくりと消化すると、デザインの見方が大きく変わりました。

アプリケーションをテストできるようにしたい場合は、アプリケーションの設計に別のアプローチを採用する必要があります。テストファーストプログラミングを行う場合、次のようなことは困難になります。 '次に、このコードにロギングを実装したい。最初に基本的なメッセージをログに記録するテストを書いてみましょう」、次に、置き換えることができないグローバルロガーを作成して使用することを強制するテストを考えます。

私はまだそのブログから得たすべての情報に苦労しており、実装するのは必ずしも容易ではなく、多くの質問があります。しかし、ミスコヘヴァリーの言っていることを理解した後で、以前に行った方法に戻る方法はありません(そう、グローバルステートとシングルトン(ビッグS)):-)


DIの場合は+1。使いたくはありませんが、少量使用したときはとても助かります。
Anurag、2010年

1
@koen:PHPでのDI / SP実装のPHPの例を示すことに注意してください。おそらく、あなたが提案した代替パターンを使用して実装された@Flaviusコード?
Alix Axel

私の回答にDIの実装とコンテナーへのリンクを追加しました。
Thomas

私はこれをすべて読んでいますが、まだ全部読んでいません。頼みたいのですが、依存性注入フレームワークは基本的にレジストリですか?
JasonDavis、2010年

いいえ、そうではありません。ただし、依存性注入コンテナはレジストリとしても機能します。私の回答に投稿したリンクを読んでください。DIの概念は本当に実際に説明されています。
Thomas

16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

これが私のやり方です。オンデマンドでオブジェクトを作成します。

Application::foo()->bar();

それは私がそれをしている方法であり、OOPの原則を尊重し、現在の方法よりもコードが少なく、コードが初めてそれを必要とするときにのみオブジェクトが作成されます。

:私が提示したのは、実際のシングルトンパターンでさえありません。シングルトンは、コンストラクター(Foo :: __ constructor())をプライベートとして定義することにより、それ自体の1つのインスタンスのみを許可します。これは、すべての「アプリケーション」インスタンスで使用できる「グローバル」変数のみです。これが、適切なOOP原則を無視しないため、その使用は有効であると私が思う理由です。もちろん、世界の何よりも、この「パターン」も使いすぎてはなりません。

私はこれが多くのPHPフレームワーク、Zend Framework、Yiiで使用されているのを見てきました。そして、あなたはフレームワークを使うべきです。どちらかはお話ししません。

補遺TDD について心配している人のために、依存関係を注入するための配線を作成することができます。次のようになります。

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

改善の余地は十分にあります。それは単なるPoCです。想像力を働かせてください。

なぜそんなことをするのですか?まあ、ほとんどの場合、アプリケーションは単体テストされません。実際には、できれば本番環境で実行されます。PHPの長所はその速度です。PHPは、Javaのように「クリーンなOOP言語」ではなく、今後もそうなることはありません。

アプリケーション内には、せいぜい1つのApplicationクラスと、各ヘルパーのインスタンスが最大で1つしかありません(上記の遅延読み込みのように)。確かに、シングルトンは悪いですが、現実世界に忠実でない場合に限ります。私の例では、そうしています。

「シングルトンは悪い」のようなステレオタイプ化された「ルール」は悪の根源であり、怠惰な人々が自分で考えたくない人のためのものです。

ええ、私は知っています。技術的に言えば、PHPマニフェストは悪いです。それでも、ハックな方法で成功した言語です。

補遺

1つの関数スタイル:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();

2
問題を処理するためのシングルトンパターンを提案することは、確実なOOP原則に反すると信じているので、私は回答に反対しました。
公園

1
@koen:あなたが言っていることは本当です、一般的に言えば、しかし私が彼の問題を理解している限り、彼はアプリケーションのヘルパーについて話していて、アプリケーション内には1つしかありません...ええと、アプリケーションです。
Flavius、

注:私が提示したものは、実際のシングルトンパターンでさえありません。シングルトンは、コンストラクターをプライベートとして定義することにより、クラスのインスタンスを1つだけ許可します。これは、すべての「アプリケーション」インスタンスで使用できる「グローバル」変数のみです。それが私がその有効性が良いOOP原則を無視しないと思う理由です。もちろん、世界の何よりも、この「パターン」も使いすぎてはなりません。
Flavius

私からも-1。それはシングルトンDPの半分にすぎないかもしれませんが、「グローバルアクセスを提供する」という醜いものです。
ちょうど誰か

2
これは確かに彼の既存のアプローチをはるかにクリーンにします。
Daniel Von Fange

15

依存性注入の概念が好きです。

「依存性注入とは、コンポーネント、コンストラクター、メソッドを介して、または直接フィールドに依存関係が与えられる場所です。(Pico Container Websiteから)

Fabien Potencierは、Dependency Injectionとそれらを使用する必要性について、非常に優れた一連の記事を書きました。彼はまた、私が本当に使いたいPimpleという名前の素敵で小さなDependency Injection Containerも提供しています(詳細はgithubを参照)。

上記のように、シングルトンの使用は好きではありません。シングルトンが優れたデザインではない理由についての良い要約は、Steve Yeggeのブログにあります。


私はPHPでの
クロージャー

私はあまりにも彼が彼のサイト上で閉鎖に関するいくつかの他の必要性のものがあります。fabien.potencier.org/article/17/...
トーマス

2
フル機能のphp 5.3サーバーを表示することはまだ一般的ではないため、主流のウェブハウスがまもなくPHP 5.3に移行することを期待しましょう
Juraj Blahunka

Zend Framework 2.0のようにPHP 5.3を必要とするプロジェクトが増えると、framework.zend.com
Thomas

1
依存関係の注入も、次の質問に対する回答として受け入れられましたdecupling from GOD objectstackoverflow.com/questions/1580210/…非常に良い例
Juraj Blahunka

9

最善の方法は、これらのリソース用のコンテナを用意することです。このコンテナーを実装する最も一般的な方法のいくつか:

シングルトン

テストが難しく、グローバルな状態を意味するため、推奨されません。(単調炎)

レジストリ

シングルトンを解消します。バグはレジストリもお勧めしません。これもシングルトンの一種だからです。(単体テストが難しい)

継承

残念ながら、PHPには多重継承がないため、これはすべてをチェーンに制限します。

依存性注入

これはより良いアプローチですが、より大きなトピックです。

伝統的な

これを行う最も簡単な方法は、コンストラクターまたはセッターインジェクションを使用することです(セッターまたはクラスコンストラクターで依存関係オブジェクトを渡す)。

フレームワーク

独自の依存関係インジェクターをロールするか、依存関係インジェクションフレームワークの一部を使用できます。ヤディフ

アプリケーションリソース

アプリケーションのブートストラップ(コンテナーとして機能)内の各リソースを初期化し、ブートストラップオブジェクトにアクセスするアプリの任意の場所にアクセスできます。

これは、Zend Framework 1.xで実装されたアプローチです。

リソースローダー

必要なときにだけ必要なリソースをロード(作成)する静的オブジェクトの一種。これは非常にスマートなアプローチです。SymfonyのDependency Injectionコンポーネントの実装など、実際の動作を確認できます

特定の層への注入

リソースは、アプリケーションのどこにも常に必要とされるわけではありません。時々、例えばコントローラー(MV C)でそれらを必要とするだけです。次に、そこにのみリソースを注入できます。

これに対する一般的なアプローチは、docblockコメントを使用して注入メタデータを追加することです。

これに対する私のアプローチをここで見てください:

Zend Frameworkで依存性注入を使用する方法 - スタックオーバーフロー

最後に、ここで非常に重要なこと、つまりキャッシュについてのメモを追加したいと思います。
一般に、選択した手法に関係なく、リソースがどのようにキャッシュされるかを考える必要があります。キャッシュはリソース自体になります。

アプリケーションは非常に大きくなる可能性があり、リクエストごとにすべてのリソースをロードすることは非常にコストがかかります。このappserver-in-php-Google Codeでのプロジェクトホスティングを含む多くのアプローチがあります。


6

オブジェクトをグローバルに利用できるようにしたい場合は、レジストリパターンが興味深いかもしれません。インスピレーションについては、Zend Registryご覧ください。

それで、レジストリ対シングルトンの質問も。


Zend Frameworkを使用したくない場合は、PHP5のレジストリパターンの優れた実装を以下に示します。phpbar.de
Thomas

私はRegistry :: GetDatabase( "master");のような型指定されたレジストリパターンを好みます。Registry :: GetSession($ user-> SessionKey()); Registry :: GetConfig( "local"); [...]そして、各タイプのインターフェースを定義します。このようにして、別の何かに使用されているキーを誤って上書きしないようにします(つまり、「マスターデータベース」と「マスター構成」がある可能性があります。インターフェースを使用することで、有効なオブジェクトのみが使用されるようにします。多くの場合、これは複数のレジストリクラスを使用して実装することもできますが、単一のクラスを使用する方が簡単で使いやすいですが、それでも利点があります
Morfildur

またはもちろん、PHPに組み込まれたもの-$ _GLOBALS
Gnuffo1

4

PHPのオブジェクトは、おそらくユニットテストからわかるように、大量のメモリを消費します。したがって、他のプロセスのためにメモリを節約するために、不要なオブジェクトをできるだけ早く破棄することが理想的です。それを念頭に置いて、すべてのオブジェクトが2つの型のいずれかに適合することがわかります。

1)オブジェクトには多くの便利なメソッドがあるか、複数回呼び出す必要がある場合があります。その場合、シングルトン/レジストリを実装します。

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2)オブジェクトは、それを呼び出すメソッド/関数の存続期間中にのみ存在します。その場合、単純な作成は、残存オブジェクト参照がオブジェクトを長く存続させないようにするために有益です。

$object = new Class();

一時オブジェクトをANYWHEREで保存すると、スクリプトの残りの部分でオブジェクトをメモリに保持することをオブジェクトへの参照が忘れられるため、メモリリークが発生する可能性があります。


3

初期化されたオブジェクトを返す関数に行きます:

A('Users')->getCurrentUser();

テスト環境では、モックアップを返すように定義できます。debug_backtrace()を使用して関数を呼び出す人の内部を検出し、さまざまなオブジェクトを返すこともできます。プログラム内で実際に何が行われているのかについて洞察を得るために、どのオブジェクトを取得したいかを登録できます。


-1

細かいマニュアルを読んでみませんか?

http://php.net/manual/en/language.oop5.autoload.php


gcbに感謝しますが、クラスのロードは私の関心事ではありません。私の質問は、よりアーキテクチャ的な性質のものです。
ペッカ

これは理論的には質問に回答するかもしれませんが、回答の本質的な部分をここに含め、参照用のリンクを提供することが望ましいでしょう
jjnguy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.