13

最近、PHPアプリケーションで依存性注入(DI)を使用する利点について学びました。ただし、依存関係のコンテナを作成する方法や、構築しているオンラインフォーラムでDIを使用する必要があるかどうかはまだわかりません。

次のコードは、ここから学んだ例に基づいて作成したDIコンテナーのバージョンです。

class ioc {

   var $db;
   var $session;
   var $user_id;

   static function newUser(static::$db, static::$user_id) {
      $user = new User($db, $user_id);
      return $user;
   }

   static function newLogin(static::$db, static::$session) {
      $login = new Login($db, $session);
      return $login;
   }

}

$user = ioc::newUser();
$login = ioc::newLogin();

少し質問があります:

1)$ database、$ sessionなど、注入された依存関係をどこでインスタンス化する必要がありますか?コンテナクラスの外部にあるのでしょうか、それともコンテナのコンストラクタの内部にあるのでしょうか。

2)他のクラス内にUserクラスの複数のインスタンスを作成する必要がある場合はどうなりますか?以前にインスタンス化された$userオブジェクトはすでに使用されているため、そのオブジェクトを挿入できません。ただし、別のクラス内に複数のUserインスタンスを作成すると、DIのルールに違反します。例えば:

class Users {

    function __construct($db, $user_id) {
        $this->db = $db;
        $this->user_id = $user_id;
    }

    function create_friends_list() {
        $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = $this->user_id");
        $st->execute();
        while($row = $st->fetch()) {
            $friend = ioc::newUser($row['user_id']);
            $friend->get_user_name();
            $friend->get_profile_picture();
        }
    }
}   

3)以前のコードをすべて書き直さなければならないことを知っているので、DIを採用するべきかどうか疑問に思っています。以前は、すべてのファイルに含まれているinitialize.phpでインスタンス化するグローバル変数に依存していました。

DIは多くのオーバーヘッドを生み出し、使用できない状況があります(私の#2の例のように)。次のサイトは、DIを使用しない多くの正当な理由を挙げている開発者からのものです。彼の議論には何かメリットがありますか?それとも私はDIを間違って使用していますか? このリンクを確認してください

4

4 に答える 4

7

$database、$session など、注入された依存関係をどこでインスタンス化する必要がありますか? コンテナ クラスの外にあるのか、それともコンテナのコンストラクタ内にあるのか。

理想的には、データベース接続とセッションがブートストラップされます。適切な DI には、すべてが登録されているベース オブジェクトのインスタンスが必要です。IOCクラスを例にとると、そのインスタンスを作成する必要があります($ioc = new IOC();)次に、ある種のサービスプロバイダークラスが必要です

$ioc->register('database', new DatabaseServiceProvider($host, $user, $pass))

データベースへの接続が必要なときはいつでも$ioc->get('database');、非常に大まかな例を渡す必要がありますが、基本的にすべてをレジストリ内に保存し、何も静的にバインドされていないという考えがわかると思います。つまり、別のインスタンス$iocを完全に作成できます。さまざまな設定により、テスト目的で別のデータベースと言う接続を簡単に作成できます。

他のクラス内に User クラスの複数のインスタンスを作成する必要がある場合はどうすればよいですか? インスタンスが既に使用されているため、以前にインスタンス化された $user オブジェクトを挿入できません。ただし、別のクラス内に複数の User インスタンスを作成すると、DI の規則に違反します。

これは一般的な問題であり、複数の異なる解決策があります。まず、DI は、ログインしているユーザーと単なるユーザーの違いを示す必要があります。おそらく、ログインしているユーザーを登録する必要がありますが、ユーザーだけではありません。ユーザークラスを通常のものにして使用します

$ioc->register('login-user', User::fetch($ioc->get('database'), $user_id));

$ioc->get('login-user')ログインしたユーザーを返すようになりました。その後、 を使用User->fetchAll($ioc->get('database'));してすべてのユーザーを取得できます。

以前のコードをすべて書き直さなければならないことを知っているため、DI を採用する必要があるかどうかも疑問に思っています。以前は、すべてのファイルに含まれている initialize.php でインスタンス化するグローバル変数に依存していました。

DI を使用するためにすべてのコードを書き直す必要がある場合は、おそらくそれを行うべきではありません。時間があれば、新しいプロジェクトの作成を検討し、古いコードの一部で作業してください。コードベースが大きい場合は、小さなプロジェクトに分割し、データの取得と保存に RESTFUL API を使用することをお勧めします。API を記述する良い例は、フォーラム検索を独自のアプリケーションに入れることです。/search/name?partial-name=bob は、単語 bob を含むすべてのユーザーを返します。それを構築して時間をかけて改善し、メイン フォーラムで使用することができます。

私の答えを理解していただければ幸いですが、さらに情報が必要な場合はお知らせください。

于 2013-01-08T13:29:33.770 に答える
2

コメントを書こうと思ったのですが、長くなってしまいました。私は専門家ではないので、数年間の練習とここ SO で学んだことから、私の見解を述べます。私の回答の一部を自由に使用または質問してください(または何もありません)。

1.- 外。コンテナは何をしますか?答えは一つであるべきです。クラスの初期化、データベースへの接続、セッションの処理などを担当する必要はありません。各クラスが行うことは 1 つだけです。

class ioc
  {
  public $db;

  // Only pass here the things that the class REALLY needs
  static public function set($var, $val)
    {
    return $this->$var = $val;
    }

  static function newDB($user, $pass)
    {
    return new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    }

  static function newUser($user_id)
    {
    return new User($db, $user_id);
    }

  static function newLogin($session)
    {
    return new Login($this->db, $session);
    }
  }

if (ioc::set('db',ioc::newDB($user, $pass)))
  {
  $user = ioc::newUser($user_id);
  $login = ioc::newLogin($session);
  }

2.- クラス内で行うべきではありません$friend = ioc::newUser($row['user_id']);iocと呼ばれるメソッドで呼び出されるクラスがあると想定していますがnewUser()、各クラスは[おそらく]他の既存のクラスに基づいてではなく、独自に動作できる必要があります。これは密結合と呼ばれます。基本的に、それがグローバル変数も使用すべきではない理由です。クラス内で使用されるものはすべて、グローバル スコープで想定されるのではなく、クラスに渡す必要があります。それがそこにあり、コードが機能することがわかっていても、クラスを他のプロジェクトで再利用できなくなり、テストがはるかに難しくなります。私は自分自身を拡張しません (PUN?) が、私がここで見つけた素晴らしいビデオを SO に入れて、さらに掘り下げることができるようにします: The Clean Code Talks - Don't Look For Things

クラスがどのようUserに動作するかはわかりませんが、これは私が行う方法です(必ずしもそうである必要はありません):

// Allow me to change the name to Friends to avoid confusions
class Friends
  {
  function __construct($db)
    {
    $this->db = $db;
    }

  function create_friends_list($user_id)
    {
    if (!empty(id))
      {
      // Protect it from injection if your $user_id MIGHT come from a $_POST or whatever
      $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = ?");
      $st->execute(array($user_id));
      $AllData = $st->fetchAll()
      return $AllData;
      }
    else return null;
    }

  // Pass the $friend object
  function get_friend_data($friend)
    {
    $FriendData = array ('Name' => $friend->get_user_name(), 'Picture' => $friend->get_profile_picture());
    return $FriendData;
    }
  }

$User = ioc::newUser($user_id);
$Friends = new Friends($db);

$AllFriendsIDs = array();
if ($AllFriendsIDs = $Friends->create_friends_list($User->get('user_id')))
  foreach ($AllFriendsIDs as $Friend)
    {
    // OPTION 1. Return the name, id and whatever in an array for the user object passed.
    $FriendData = $Friends->get_friend_data(ioc::newUser($Friend['user_id']));
    // Do anything you want with $FriendData

    // OPTION 2. Ditch the get_friend_data and work with it here directly. You're already in a loop.
    // Create the object (User) Friend.
    $Friend = ioc::newUser($Friend['user_id']);
    $Friend->get_user_name();
    $Friend->get_profile_picture();
    }

私はそれをテストしていないので、おそらくいくつかの小さなバグがあります。

3.-コーディング中に学習している場合、多くのことを書き直す必要があります。すべてを書き直す必要はありませんが、クラス/メソッドだけを書き直し、すべてのコードにいくつかの規則を採用するように、最初から何かを正しく行うようにしてください。たとえば、関数/メソッド内から決してエコーしないでください。常に戻り、外部からエコーします。はい、それだけの価値があると思います。何かを書き直すだけで 1 時間か 2 時間無駄にするのは悪いことですが、やらなければならない場合は実行してください。

PS、申し訳ありませんが、ブラケットのスタイルをどこでも変更しました。


EDIT他の回答を読んで、iocオブジェクトを使用してデータベースに接続するべきではありませんが、それを使用して新しいオブジェクトを完全に作成する必要があります。私が何を意味するかを見るために上で編集しました。

于 2013-01-08T13:56:26.423 に答える
1

あなたが尋ねた質問には、私がコメントで尋ねた非常に重要な問題が 1 つあります。前に戻って既存のクラスの些細なリファクタリングを達成するために進行を一時停止する可能性を検討するときはいつでも、その見返りについて真剣に考える必要があります。

あなたは答えました

これは少し複雑 (そして非常に異なる) であり、現時点で実装する価値があるよりも多くの作業が必要です。私はちょうど何かを本当に素早くハックして、それが牽引力を得るかどうかを見ようとしています.

IoC コンテナを使用して DI を適切に実装するには、次のことを行う必要があることに注意してください。

  • ブートストラップで IoC コンテナとレジストリを実装する
  • 依存関係を持つすべての既存のクラスを変更して、IoC コンテナー自体に依存せずにセッター インジェクションを許可する
  • 必要に応じたテストの書き直し/再構築

私自身の個人的な見解と好みを反映した 2 番目の箇条書きのメモ: DI を念頭に置いて開発するのは難しい場合があり、DIが提供できる完全な報酬が必要な場合があります。最大のメリットの 1 つは、完全に分離されたオブジェクトです。すべてがまだ IoC コンテナーに依存している場合、その完全な部分を取得することはできません。

特に今までパターンを検討していなかった場合は、これは大変な作業です。確かに、再利用可能で簡単にテストできるオブジェクトがたくさんあるなど、明らかなメリットがあります。ただし、すべてのリファクタリングと同様に、新しい機能や機能を追加または仕上げるというコンテキストでは、新しいことは何も達成されません。テストの難易度や密結合がその妨げになっていますか? それはあなたが秤量しなければならないものです。

代わりに、新しいコードを書くときにパターンを念頭に置いておくことをお勧めします。依存関係を注入するセッター メソッドを提供します。これは、手動または IoC コンテナーを介して利用できます。ただし、クラスが注入されていない場合は、引き続きクラスを作成し、コンストラクターでの構成を避けてください。

その時点で、制御の反転にはるかに適したクラスのコレクションが得られます (それが将来追求したいパターンである場合)。それがうまく機能し、実際に設計上の決定に組み込みたい場合は、後で簡単にリファクタリングして JIT ロジックを削除できます。

結論として、完全に実装しようとしても、今すぐ適切に実装しようとすると、混乱するだけになるのではないかと思います。クラスの書き方を今後変更することはできますが、私は戻って全面的に実装しようとはしません。

于 2013-01-09T01:46:36.497 に答える
1

init.php のグローバルの代わりに、次のようにオブジェクトを定義します。

ioc::register('user', function() {
 return new User();
});

そして、 create_friends_list メソッド内で次を使用します。

ioc::get('user')->newUser($user_id);

これは本当に単純な実装です。チェックアウト: http://net.tutsplus.com/tutorials/php/dependency-injection-huh/?search_index=2

また

http://net.tutsplus.com/tutorials/php/dependency-injection-in-php/?search_index=1

詳細については。

于 2013-01-08T13:10:47.510 に答える