2013-01-08 47 views
13

我最近了解到在我的PHP應用程序中使用依賴注入(DI)的優勢。但是,我仍然不確定如何爲依賴關係創建容器,或者我是否應該使用DI來創建我正在構建的在線論壇。如何構建PHP依賴注入容器

以下代碼是我根據我從here獲悉的示例製作的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)我應該在哪裏我的實例注入相關性,比如$數據庫,$會議等?它會在容器類之外,還是在容器的構造函數之內。

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錯誤? check this link

+0

我已經讀了幾次這個網站,主要是關於作者是多麼憤怒的迷戀。顯然他的言論自由延伸到在他們自己的博客上侮辱人,而刪除他的人是納粹。哦,親愛的......':)' – halfer

+0

爲了更直接地回答這個問題(!),Symfony2有一個[獨立的DI組件](http://symfony.com/doc/current/components/dependency_injection/index.html ),你可能會覺得值得一看。 – halfer

+0

查看http://docs.codehaus.org/display/PICO/IoC+Types瞭解各種方法的一些信息。 – Gordon

回答

1

而是在你的init.php全局的定義你的對象,如:

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

而且inisde您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

以獲取更多信息。

+0

他實際上鍊接到問題中的第一個(可能沒有注意到,它是第二句中的'here') –

+0

我的不好,沒有看到鏈接。 –

7

我應該在哪裏實例化我注入的依賴關係,比如$ database,$ session等?它會在容器類之外,還是在容器的構造函數之內。

理想的數據庫連接和會話會在自舉。正確的DI需要這一切都被註冊爲基本對象的實例。所以,把你的IOC類爲例,你需要做的是($ioc = new IOC();)的實例,那麼你需要某種形式的服務提供者類的說

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

現在每次你想你只需要與數據庫的連接通過$ioc->get('database');一個非常粗略的例子,但我認爲你可以看到這個想法基本上是將所有內容都存儲在註冊表中,並且沒有任何內容是靜態綁定的,這意味着你可以創建另一個具有完全不同設置的實例,從而輕鬆創建連接來表示不同的設置數據庫用於測試目的。

如果我需要在其他類中創建User類的多個實例,該怎麼辦?我無法注入先前實例化的$ user對象,因爲該實例已被使用。但是,在另一個類中創建多個User實例會違反DI的規則。

這是一個常見問題,有多種不同的解決方案。首先你的DI應該顯示登錄用戶和用戶之間的區別。您可能想要註冊您的登錄用戶,但不是任何用戶。使您的用戶級別正常並使用

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

so now $ioc->get('login-user')返回您登錄的用戶。然後您可以使用User->fetchAll($ioc->get('database'));來獲取所有用戶。

我想知道如果我應該採用DI,知道我必須重寫所有我以前的代碼。我以前一直依靠全局變量,我在我的initialize.php中實例化了,它包含在我的所有文件中。

如果你需要重寫所有的代碼來使用DI,你不應該這樣做。如果你有時間,也許可以考慮製作一個新項目並在你的一些舊代碼中工作。如果您的代碼量很大,我建議您考慮將其分解爲更小的項目,並使用RESTFUL API來獲取和保存數據。編寫API的好例子是將你的論壇搜索放到它自己的應用程序/搜索/名稱中?partial-name = bob將返回所有使用bob這個詞的用戶。你可以建立起來,使之更好地隨着時間的推移,並在主論壇

我希望你明白我的答案,但如果你需要任何更多的信息讓我知道使用它。

+0

你能詳細解釋你正在使用的'register'和'get'方法嗎?謝謝! – Anonymous

+0

註冊和獲取方法可以用許多不同的方法來實現,但以其最基本的方式,它只是將對象添加到數組中,例如: public $ registry = array(); 函數註冊($ name,$ value){ if(isset($ this-> registry [$ name]){ 拋出新的異常($ name。'已被註冊!「); } $ this-> registry [$ name] = $ value; } } 你也可以註冊字符串和數組,並使用PHP的新版本,你可以註冊非常酷的lambda函數,每次你可以執行函數內的所有代碼。 –

+0

我認爲如果你對這些想法有所瞭解,我會強烈建議開始一個像silex或laraval這樣的項目,這兩個項目都建立在DI之後,你可以跟隨和建立起來。 –

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(),而每個類應該能夠獨立執行,而不是基於其他現有類。這被稱爲tight coupling。基本上,這就是爲什麼你不應該使用全局變量。任何在類中使用的東西都應該傳遞給它,而不是在全球範圍內假設。即使你知道它在那裏並且你的代碼有效,它也會讓這個類不能用於其他項目,而且測試起來也更加困難。我不會擴展自己(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,對不起,我改變了你的支架風格無處不在。


編輯 閱讀其他的答案,而你不應該連接到您的IOC對象的數據庫,它應該是perfeclty罰款創建一個新的對象吧。上面編輯看看我的意思。

+0

Ty的幫助! 1)我從來沒有見過在容器中使用的設置方法。我假設它用於創建任何我需要注入容器類中的方法的實例?另外,它不應該是靜態函數集($ var,$ val){return $ this - > $ var = $ val}? 2)你說不要使用$ friend = ioc :: newUser($ row ['user_id']);在我的班級裏面,但如果我在班裏需要它呢?所以創建列表的唯一方法是發送一個數組並遍歷頁面上的數組?似乎很麻煩,不要在課堂上完成這些代碼。如果我在一個頁面上有多個列表會怎麼樣? – Anonymous

+0

1)錯字,更正。 2)我實際上在'get_friend_data($ friend)'中使用了一個對象。實際上傳遞的'$ friend'是一個對象。只傳遞你需要的東西,但是如果你需要的話,可以傳遞整個對象給一個類(儘管最好避免它)..你必須能夠自己測試'Friends'類* ,爲此你應該能夠做到這一點,而不需要有一個真正的用戶類。我的代碼中的選項1在裏面完成。我不知道你的'create_friends_list()'函數返回了什麼,所以我不能正確回答那個部分,因此留下了兩個選項供你選擇。 –

+0

調用我的容器對象的好名字是什麼? $ ioc或$ reg是否可以接受?另外,容器內的方法是否需要靜態?例如,我可以做$ user = $ reg-> newUser(); ?謝謝。 – Anonymous

1

您提出的問題有一個非常重要的問題,我在評論中詢問了這個問題。任何時候,當你考察暫停前進的可能性,以便回到原來的階級並重新完成一個不重要的因素時,你必須真正考慮收益。

replied

這似乎有點複雜(非常不同)和更多的工作比它的價值在這一點上實現。我只是想快速破解一些事情,看看它是否獲得了牽引力。

請記住,你下面的事情做,如果你想實現與IoC容器正確的DI:

  • 在引導落實IoC容器和註冊表
  • 修改現有的所有類,具有依賴性以允許設置注入,而不取決於IoC容器本身
  • 根據需要重新寫入/重新構建測試

第二子彈一張紙條,上面反映了我個人的看法和喜好,考慮到與DI開發可和你的全額獎勵它可以給。最大的回報之一是完全解耦的對象,如果所有東西都依賴於IoC容器,則不會獲得完整的的一部分。

這是很多工作,尤其是如果你還沒有考慮到現在的模式。當然,你會得到明顯的好處,比如當你完成了大量的可重用和易於測試的對象。但是,與所有重新分解一樣,在添加或完成新的功能和特性的情況下,不會有新的事情發生。難度測試或緊耦合阻礙了這一點嗎?這是你必須權衡的。

我建議你做的事情是在你寫新代碼的時候保持模式記住。提供setter方法來注入依賴關係,可以手動或通過IoC容器來使用依賴關係。但是,如果沒有注入任何內容,並且在構造函數中避免組合,那麼讓你的類繼續創建它們即可

在這一點上,如果這是您未來想追求的模式,那麼您將擁有一系列類別,這些類別藉助好得多來反轉控制。如果它適用於您,並且您確實希望將其納入您的設計決策中,那麼您可以輕鬆地重新考慮以後再刪除JIT邏輯。

總而言之,如果您嘗試完全實現它,恐怕最終會出現這種情況,而正確現在是一團糟。你可以改變你現在寫的課程,但我不會回去嘗試全面實施它。