警告!
Pimple 現在已關閉以進行更改。不會添加任何新功能,也不會接受任何外觀變更。唯一接受的更改是與較新的 PHP 版本的兼容性和安全性問題修復。
警告!
這是 Pimple 3.x 的文檔。如果您使用的是 Pimple 1.x,請閱讀 Pimple 1.x 文件。閱讀 Pimple 1.x 程式碼也是了解更多有關如何建立簡單依賴注入容器的好方法(最近版本的 Pimple 更專注於效能)。
Pimple 是一個小型的 PHP 依賴注入容器。
在您的專案中使用 Pimple 之前,請將其新增至您的composer.json
檔案:
$ ./composer.phar require pimple/pimple " ^3.0 "
建立容器就是建立一個Container
實例:
use Pimple Container ;
$ container = new Container ();
與許多其他依賴注入容器一樣,Pimple 管理兩種不同類型的資料:服務和參數。
服務是作為更大系統的一部分執行某些操作的物件。服務範例:資料庫連線、範本引擎或郵件程式。幾乎任何全域物件都可以是服務。
服務由傳回物件實例的匿名函數定義:
// define some services
$ container [ ' session_storage ' ] = fn ( $ c ) => new SessionStorage ( ' SESSION_ID ' );
$ container [ ' session ' ] = fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]);
請注意,匿名函數可以存取目前容器實例,從而允許引用其他服務或參數。
由於物件僅在獲得它們時才創建,因此定義的順序並不重要。
使用定義的服務也非常簡單:
// get the session object
$ session = $ container [ ' session ' ];
// the above call is roughly equivalent to the following code:
// $storage = new SessionStorage('SESSION_ID');
// $session = new Session($storage);
預設情況下,每次您取得服務時,Pimple 都會傳回該服務的相同實例。如果您希望為所有呼叫傳回不同的實例,請使用factory()
方法包裝您的匿名函數
$ container [ ' session ' ] = $ container -> factory ( fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]));
現在,每次呼叫$container['session']
都會傳回會話的一個新實例。
定義參數可以簡化從外部對容器的配置並儲存全域值:
// define some parameters
$ container [ ' cookie_name ' ] = ' SESSION_ID ' ;
$ container [ ' session_storage_class ' ] = ' SessionStorage ' ;
如果您更改session_storage
服務定義,如下所示:
$ container [ ' session_storage ' ] = fn ( $ c ) => new $ c [ ' session_storage_class ' ]( $ c [ ' cookie_name ' ]);
現在,您可以透過覆寫cookie_name
參數輕鬆變更 cookie 名稱,而無需重新定義服務定義。
因為 Pimple 將匿名函數視為服務定義,所以您需要使用protect()
方法包裝匿名函數,以將它們儲存為參數:
$ container [ ' random_func ' ] = $ container -> protect ( fn () => rand ());
在某些情況下,您可能希望在定義服務定義後對其進行修改。您可以使用extend()
方法來定義在服務建立後立即在服務上執行的附加程式碼:
$ container [ ' session_storage ' ] = fn ( $ c ) => new $ c [ ' session_storage_class ' ]( $ c [ ' cookie_name ' ]);
$ container -> extend ( ' session_storage ' , function ( $ storage , $ c ) {
$ storage ->. . .();
return $ storage ;
});
第一個參數是要擴展的服務的名稱,第二個參數是存取物件實例和容器的函數。
如果您一遍又一遍地使用相同的程式庫,您可能會想要在下一個專案中重複使用一些服務;透過實作PimpleServiceProviderInterface
將您的服務打包到提供者中:
use Pimple Container ;
class FooProvider implements Pimple ServiceProviderInterface
{
public function register ( Container $ pimple )
{
// register some services and parameters
// on $pimple
}
}
然後,在容器上註冊提供者:
$ pimple -> register ( new FooProvider ());
當您存取物件時,Pimple 會自動呼叫您定義的匿名函數,該函數會為您建立服務物件。如果您想要取得該函數的原始存取權限,可以使用raw()
方法:
$ container [ ' session ' ] = fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]);
$ sessionFunction = $ container -> raw ( ' session ' );
由於歷史原因, Container
類別沒有實作 PSR-11 ContainerInterface
。然而,Pimple 提供了一個幫助器類,可以讓您將程式碼與 Pimple 容器類別解耦。
PimplePsr11Container
類別可讓您使用PsrContainerContainerInterface
方法存取底層 Pimple 容器的內容:
use Pimple Container ;
use Pimple Psr11 Container as PsrContainer ;
$ container = new Container ();
$ container [ ' service ' ] = fn ( $ c ) => new Service ();
$ psr11 = new PsrContainer ( $ container );
$ controller = function ( PsrContainer $ container ) {
$ service = $ container -> get ( ' service ' );
};
$ controller ( $ psr11 );
有時,一個服務需要存取多個其他服務,但不確定所有這些服務都會實際使用。在這些情況下,您可能想要服務的實例化是惰性的。
傳統的解決方案是注入整個服務容器以僅獲取真正需要的服務。但是,不建議這樣做,因為它為服務提供了對應用程式其餘部分的過於廣泛的存取權限,並且隱藏了它們的實際依賴關係。
ServiceLocator
旨在透過提供對一組預先定義服務的存取權並僅在實際需要時實例化它們來解決此問題。
它還允許您使用與註冊服務不同的名稱來提供服務。例如,您可能希望使用一個對象,該對象期望EventDispatcherInterface
的實例在名稱event_dispatcher
下可用,而您的事件調度程序已在名稱dispatcher
下註冊:
use Monolog Logger ;
use Pimple Psr11 ServiceLocator ;
use Psr Container ContainerInterface ;
use Symfony Component EventDispatcher EventDispatcher ;
class MyService
{
/**
* "logger" must be an instance of PsrLogLoggerInterface
* "event_dispatcher" must be an instance of SymfonyComponentEventDispatcherEventDispatcherInterface
*/
private $ services ;
public function __construct ( ContainerInterface $ services )
{
$ this -> services = $ services ;
}
}
$ container [ ' logger ' ] = fn ( $ c ) => new Monolog Logger ();
$ container [ ' dispatcher ' ] = fn ( $ c ) => new EventDispatcher ();
$ container [ ' service ' ] = function ( $ c ) {
$ locator = new ServiceLocator ( $ c , array ( ' logger ' , ' event_dispatcher ' => ' dispatcher ' ));
return new MyService ( $ locator );
};
如果使用該集合的類別僅需要在稍後階段呼叫其方法之一時對其進行迭代,則在陣列中傳遞服務實例集合可能會效率低下。如果儲存在集合中的服務之一與使用該服務的類別之間存在循環依賴關係,也可能會導致問題。
ServiceIterator
類別可以幫助您解決這些問題。它在實例化期間接收服務名稱列表,並在迭代時檢索服務:
use Pimple Container ;
use Pimple ServiceIterator ;
class AuthorizationService
{
private $ voters ;
public function __construct ( $ voters )
{
$ this -> voters = $ voters ;
}
public function canAccess ( $ resource )
{
foreach ( $ this -> voters as $ voter ) {
if ( true === $ voter -> canAccess ( $ resource )) {
return true ;
}
}
return false ;
}
}
$ container = new Container ();
$ container [ ' voter1 ' ] = fn ( $ c ) => new SomeVoter ();
$ container [ ' voter2 ' ] = fn ( $ c ) => new SomeOtherVoter ( $ c [ ' auth ' ]);
$ container [ ' auth ' ] = fn ( $ c ) => new AuthorizationService ( new ServiceIterator ( $ c , array ( ' voter1 ' , ' voter2 ' ));