警告!
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 ' ));