lucatume / di52
一个兼容 PHP 5.6 的依赖注入容器。
Requires
- php: >=5.6
- ext-json: *
- psr/container: ^1.0
Requires (Dev)
- phpunit/phpunit: <10.0
- dev-master
- 3.3.7
- 3.3.6
- 3.3.5
- 3.3.4
- 3.3.3
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.1
- 3.2.0
- 3.1.1
- 3.1.0
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.12
- 2.0.11
- 2.0.10
- 2.0.9
- 2.0.8
- 2.0.7
- 2.0.6
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.4.5
- 1.4.4.1
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.1b
- 1.4.0
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.6
- 1.2.6-alpha
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.3
- 1.0.2
- 1.0.1
This package is auto-updated.
Last update: 2024-08-26 15:26:09 UTC
README
一个受 Laravel IOC 和 Pimple 启发的 PHP 5.6+ 兼容依赖注入容器,它在新版 PHP 上表现更佳。
容器功能快速概览
- 自动装配 - 容器将使用反射来查找应该构建哪个类以及如何构建,它几乎是魔法般的。
- 灵活 - 旧代码?带有大量副作用的大构造函数?容器提供自动装配,而不将其作为必需条件。它将适应现有代码,而不会要求您的代码适应它。
- 致命错误处理 - 在 PHP 7.0+ 中,容器将负责处理在类文件加载时可能发生的致命错误,并进行处理。
- 快速 - 容器针对速度进行了优化,尽可能多地利用所需的 PHP 兼容性。
- 灵活的默认模式 - 提供单例(最多构建一次)和原型(每次都构建新实例)默认模式。
- 全局应用 - 像使用
App::get($service)->doStuff()
一样?App
门面允许将 DI 容器用作全局可用的服务定位器。 - PSR-11 兼容 - 容器完全符合 PSR-11 规范。
- 适用于 WordPress 和其他事件驱动框架 - 容器 API 提供类似于
callback
和instance
的方法,可以轻松集成需要将回调函数连接到事件的事件驱动框架,如 WordPress。 - 服务提供者 - 为了使您的代码井井有条,该库提供了一个 高级服务提供者实现。
目录
- 代码示例
- 安装
- 从版本 2 升级到版本 3
- 从版本 3.2 升级到版本 3.3
- 依赖注入的快速入门
get
的强大功能- 存储变量
- 绑定实现
- 将绑定实现绑定到缩写名
- 上下文绑定
- 绑定装饰器链
- 标记
- 回调方法
- 服务提供者
- 自定义容器
代码示例
在应用程序引导文件中,我们定义组件如何组合在一起
<?php /** * The application bootstrap file: here the container is provided the minimal set of instructions * required to set up the application objects. */ namespace lucatume\DI52\Example1; use lucatume\DI52\App; use lucatume\DI52\Container; require_once __DIR__ . '/vendor/autoload.php'; // Start by building an instance of the DI container. $container = new Container(); // When an instance of `TemplateInterface` is required, build and return an instance // of `PlainPHPTemplate`; build at most once (singleton). $container->singleton( TemplateInterface::class, static function () { return new PlainPHPTemplate(__DIR__ . '/templates'); } ); // The default application Repository is the Posts one. // When a class needs an instance of the `RepositoryInterface`, then // return an instance of the `PostsRepository` class. $container->bind(RepositoryInterface::class, PostsRepository::class); // But the Users page should use the Users repository. $container->when(UsersPageRequest::class) ->needs(RepositoryInterface::class) ->give(UsersRepository::class); // Bind primitive values, e.g. public function __construct( int $per_page ) {} $container->when(UsersPageRequest::class) ->needs('$per_page') ->give(10); // Fetch the above class without any further definitions $container->get(UsersPageRequest::class) // The `UsersRepository` will require a `DbConnection` instance, that // should be built at most once (singleton). $container->singleton(DbConnection::class); // Set the routes. $container->bind('home', HomePageRequest::class); $container->bind('users', UsersPageRequest::class); // Make the container globally available as a service locator using the App. App::setContainer($container);
在应用程序入口点,即 index.php
文件中,我们将根据引导文件中设置的规则 懒加载 解析整个依赖树
<?php use lucatume\DI52\App; require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/bootstrap.php'; $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $route = basename(basename($path, '.php'), '.html') ?: 'home'; App::get($route)->serve(); ?> ?>
就这样。
安装
使用 Composer 安装库
composer require lucatume/di52
在项目入口点包含Composer
自动加载文件,并创建一个新的容器实例以开始使用它
<?php require_once 'vendor/autoload.php'; $container = new lucatume\DI52\Container(); $container->singleton(DbInterface::class, MySqlDb::class);
如果您希望将依赖注入容器作为全局服务定位器使用,则可以使用lucatume\DI52\App
<?php require_once 'vendor/autoload.php'; lucatume\DI52\App::singleton(DbInterface::class, MySqlDb::class);
请参阅上面的示例以获取更多使用示例。
从版本 2 升级到版本 3
库版本3.0.0
的主要更改是放弃与PHP 5.2的兼容性,要求至少使用PHP 5.6。库已测试到PHP 8.1。
如果您在项目中使用DI52版本2,那么您应该不需要做任何事情。版本3的新命名空间类是对版本2对应类的别名,例如tad_DI52_Container
是对lucatume\di52\Container
的别名,而tad_DI52_ServiceProvider
是对lucatume\di52\ServiceProvider
的别名。
尽管如此,我建议进行更新以获得small performance gain
,并用新的命名空间类名代替PHP 5.2兼容的类名
- 将
tad_DI52_Container
的使用替换为lucatume\di52\Container
- 将
tad_DI52_ServiceProvider
的使用替换为lucatume\DI52\ServiceProvider
新版本实现了PSR-11兼容性,并从make
更改为get
来获取容器中的对象实例的主要方法。不要担心,lucatume\di52\Container::make
方法仍然存在:它只是lucatume\di52\Container::get
的一个别名。为了获得另一个小的性能提升,请将tad_DI52_Container::make
的使用替换为lucatume\di52\Container::get
。
这就全部了。
从版本 3.2 升级到版本 3.3
库的3.3.0版本删除了aliases.php
文件,该文件之前有助于加载非PSR命名空间类名。但是,如果您在项目中使用tad_DI52_Container
和tad_DI52_ServiceProvider
类,您可以通过在项目引导文件中添加几行代码来设置别名,以确保代码继续按预期工作
<?php $aliases = [ ['lucatume\DI52\Container', 'tad_DI52_Container'], ['lucatume\DI52\ServiceProvider', 'tad_DI52_ServiceProvider'] ]; foreach ($aliases as list($class, $alias)) { if (!class_exists($alias)) { class_alias($class, $alias); } }
依赖注入的快速入门
什么是依赖注入?
依赖注入(DI)容器是一种旨在使依赖注入变得可能且易于管理的工具。依赖项通过类构造方法中的类型提示指定。
class A { private $b; private $c; public function __construct(B $b, C $c){ $this->b = $b; $this->c = $c; } }
类A
的任何实例都依赖于B
和C
类的实现。"注入"发生在将类A
的依赖项传递给它时,"注入"到其构造方法中,而不是在类内部创建。
$a = new A(new B(), new C());
类型提示的灵活性允许向A
注入不仅仅是B
和C
的实例,还可以是任何扩展这两个类的类的实例。
class ExtendedB extends B {} class ExtendedC extends C {} $a = new a(new ExtendedB(), new ExtendedC());
PHP允许类型提示不仅仅是具体的实现(类),还可以是接口
。
class A { private $b; private $c; public function __construct(BInterface $b, CInterface $c){ $this->b = $b; $this->c = $c; } }
这进一步扩展了依赖注入的可能性,并避免了代码的严格耦合。
class B implements BInterface {} class C implements CInterface {} $a = new a(new B(), new C());
什么是 DI 容器?
类B
和C
是接口的具体(即可以实例化)实现,而接口可能永远不会改变,但实现可能会在代码的生命周期中改变:这是依赖倒置原则或"依赖于抽象,非具体"。如果BInterface
的实现从B
更改为BetterB
,那么我必须更新所有构建A
实例的代码,以使用BetterB
代替B
。
// before $a = new A(new B(), new C()); //after $a = new A(new BetterB(), new C());
在较小的代码库中,这可能是一个快速的解决方案,但随着代码的增长,它将变得越来越不适用。向其中添加类证明了这一点,当依赖开始堆叠时。
class D implements DInterface{ public function __construct(AInterface $a, CInterface $c){} } class E { public function __construct(DInterface $d){} } $a = new A (new BetterB(), new C()); $d = new D($a, $c); $e = new E($d);
这种方法的另一个问题是,为了注入,必须立即构建类,请参见上面的$a
和$d
,以向$e
提供数据,这立即产生了“懒惰”实例化的成本。如果$e
从未被使用,那么在构建它所投入的努力,即PHP构建$a
、$b
、$c
、$d
和最终$e
所花费的时间和资源,都将被浪费。一个依赖注入容器将负责构建所需的唯一对象,并负责解决嵌套依赖。
需要
E
的实例吗?我将构建B
和C
的实例来构建A
的实例,再构建D
的实例,最终构建并返回E
的实例。
什么是服务定位器?
“服务定位器”是一个对象或函数,它将回答你的代码提出的问题。
$database = $serviceLocator->get('database');
简单地说,“我不在乎它是如何构建的,也不在乎它从哪里来,给我当前的数据库服务实现。”。
由于服务定位器知道在需要时如何构建服务定位器将提供的服务,因此服务定位器通常是全局可用的DI容器。显然,当DI容器全局可用时,它就提供了一个良好的服务定位器实现。服务定位器和DI容器这两个概念通常被混为一谈,因为DI容器在全局可用时,就构成了一个好的服务定位器实现。
这是一个例子:lucatume\DI52\App
类:它将通过静态方法公开一个全局可用的lucatume\DI52\Container
类的实例。
<?php use lucatume\DI52\Container; use lucatume\DI52\App; // This is a DI Container. $diContainer = new Container(); // If we make it globally-available, then it will be used by the Service Locator (the `App` class). App::setContainer($diContainer); // Register a binding in the DI Container. $diContainer->singleton('database', MySqlDb::class); // We can now globally, i.e. anywhere in the code, access the `db` service. $db = App::get('database');
由于lucatume\DI52\App
类代理对容器的调用,因此示例可以更简短。
<?php use lucatume\DI52\App; // Register a binding in the App (Service Locator). App::singleton('database', MySqlDb::class); // We can now globally, i.e. anywhere in the code, access the `db` service. $db = App::get('database');
构建模板
容器只需要告诉一次对象应该如何构建。对于容器来说,很容易理解,如果一个类通过类型提示需要A
的实例,它将需要一个新实例的A
,但利用DI容器编写的松耦合代码可能会使用interface
而不是具体的class
。当对象的构造函数方法请求某个interface
时,告诉容器应该实例化哪个具体的class
,这被称为“将绑定和实现绑定到接口”。虽然依赖注入可以在除构造函数外的其他方法中实现,但di52目前支持的是这一点;如果你想要了解更多,网上有很多好的参考资料, Fabien Potencier的这篇文章是一个很好的起点:什么是依赖注入。
get
的强大功能
在底层,容器是一个依赖解析和注入机器:给它的get
方法一个类,它将读取类中类型提示的依赖,构建它们并将它们注入到类中。
// file ClassThree.php class ClassThree { private $one; private $two; public function __construct(ClassOne $one, ClassTwo $two){ $this->one = $one; $this->two = $two; } } // The application bootstrap file use lucatume\DI52\Container; $container = new Container(); $three = $container->get('ClassThree');
阅读以下段落时请记住这一点。
存储变量
在其最基本的使用场景中,容器可以存储变量。
use lucatume\DI52\Container; $container = new Container(); $container->setVar('number', 23); $number = $container->getVar('number');
由于容器将任何可调用对象视为工厂(见下文),可调用对象必须通过容器的protect
方法来保护。
$container = new tad_DI52_Container(); $container->setVar('randomNumberGenerator', $container->protect(function($val){ return mt_rand(1,100) + 23; })); $randomNumberGenerator = $container->getVar('randomNumberGenerator');
protect
方法告诉容器,当通过get
获取randomNumberGenerator
别名时,我们不希望运行函数并获取其结果,而是希望获取函数本身。
绑定实现
通过类似Laravel服务容器公开的API来告诉容器应该构建什么以及何时构建。虽然内部工作原理不同,但好的想法(向Laravel的创建者和维护者致敬)被重新使用。以下是一个使用上述示例的重用示例。
use lucatume\DI52\Container; $container = new Container(); // Bind to a class name. $container->bind(AInterface::class, A::class); // Bind to a Closure. $container->bind(BInterface::class, function(){ return new BetterB(); }); // Bind to a constructor and methods that should be called on the built object. $container->bind(CInterface::class, LegacyC::class, ['init','register']); // Bind to a factory method. $container->bind(D::interface, [DFactory::class,'buildInstance']) // Bind to an object, it will be a singleton by default. $container->bind(E::interface, new EImplementation()); $e = $container->get(F::class);
《get》方法将在请求时构建《F》对象,解析其需求到已绑定的实现。当使用《bind》方法时,每次请求都会返回一个新实例的已绑定实现;这可能会产生不希望的行为,尤其是对于成本高昂的对象(例如需要连接的数据库驱动程序):在这种情况下,应使用《singleton》方法。
use lucatume\DI52\Container; $container = new Container(); $container->singleton(DBDriverInterface::class, MYSqlDriver::class); $container->singleton(RepositoryInterface::class, MYSQLRepository::class); $container->get(RepositoryInterface::class);
使用《singleton》方法将实现绑定到接口,告诉容器实现仅在第一次构建:任何后续对该接口的调用都应返回相同的实例。可以通过再次调用《bind》或《singleton》方法并指定不同的实现来在任意时刻重新定义实现。
您可以通过查看未绑定类部分来自定义容器解析未绑定类的方式。
将绑定实现绑定到缩写名
该容器深受Pimple的启发,并提供了PHP 5.3+ DI容器的某些功能。
use lucatume\DI52\Container; $container = new Container(); // Storing vars using the ArrayAccess API. $container['db.name'] = 'appDb'; $container['db.user'] = 'root'; $container['db.pass'] = 'secret'; $container['db.host'] = 'localhost:3306'; // Bindings can be set using ArrayAccess methods. $container['db.driver'] = MYSQLDriver::class; // Bound closures will receive the container instance as argument. $container['db.connection'] = function($container){ $host = $container['db.host'] $user = $container['db.user'], $pass = $container['db.pass'], $name = $container['db.name'], $dbDriver = $container['db.driver']; $dbDriver->connect($host, $user, $pass, $name); return new DBConnection($dbDriver); }; // Equivalent to $container->get('db.connection'); $dbConnection = $container['db.connection']; // Using ArrayAccess API to store a closure as a variable. $container['uniqid'] = $container->protect(function(){ return uniqid('id', true); });
Pimple提供的《factory》方法没有替代品:应使用《bind》方法。
上下文绑定
借鉴了Laravel容器的一个优秀想法,存在上下文绑定(支持上述所有绑定可能性)。上下文绑定解决了不同对象需要相同接口(或类,见上文)的不同实现的问题。
use lucatume\DI52\Container; $container = new Container(); /* * By default any object requiring an implementation of the `CacheInterface` * should be given the same instance of `Array Cache` */ $container->singleton(CacheInterface::class, ArrayCache::class); $container->bind(DbCache::class, function($container){ $cache = $container->get(CacheInterface::class); $dbCache = new DbCache($cache); return $dbCache; }); /* * But when an implementation of the `CacheInterface` is requested by * `TransactionManager`, then it should be given an instance of `Array Cache`. */ $container->when(TransactionManager::class) ->needs(CacheInterface::class) ->give(DbCache::class); /* * We can also bind primitives where the container doesn't know how to auto-wire * them. */ $container->when(MysqlOrm:class) ->needs('$dbUrl') ->give('mysql://user:password@127.0.0.1:3306/app'); /* * When primitives are bound to a class the container will correctly resolve them when building the class * bound to an interface. */ $container->bind(ORMInterface::class, MysqlOrm::class); // The `ORMInterface` will be resolved an instance of the `MysqlOrm` class, with the `$dbUrl` argument set correctly. $orm = $container->get(ORMInterface::class);
绑定装饰器链
《Decorator pattern》允许在不创建扩展的情况下扩展实现的功能,同时利用接口。容器允许使用《bindDecorators》和《singletonDecorators》将“装饰器链”绑定到接口(或类似Pimple的slug)上。这两种方法是对装饰器的《bind》和《singleton》等效方法。
use lucatume\DI52\Container; $container = new Container(); $container->bind(RepositoryInterface::class, PostRepository::class); $container->bind(CacheInterface::class, ArrayCache::class); $container->bind(LoggerInterface::class, FileLogger::class); // Decorators are built left to right, outer decorators are listed first. $container->bindDecorators(PostEndpoint::class, [ LoggingEndpoint::class, CachingEndpoint::class, BaseEndpoint::class ]);
与《bind》或《singleton》调用类似,您可以使用《afterBuildMethods》参数指定一组在装饰器链构建后要调用的方法。
use lucatume\DI52\Container; $container = new Container(); $container->bind(RepositoryInterface::class, PostRepository::class); $container->bind(CacheInterface::class, ArrayCache::class); $container->bind(LoggerInterface::class, FileLogger::class); // Decorators are built left to right, outer decorators are listed first. $container->bindDecorators(PostEndpoint::class, [ LoggingEndpoint::class, CachingEndpoint::class, BaseEndpoint::class ], ['register']);
默认情况下,将只调用《register》方法在基础实例上,装饰器链右侧的实例。
在上面的示例中,只有《BaseEndpoint::register》将被调用。
如果您需要在每个实例构建后都调用相同的after-build方法集,可以将《afterBuildAll》参数的值设置为《true》。
use lucatume\DI52\Container; $container = new Container(); $container->bind(RepositoryInterface::class, PostRepository::class); $container->bind(CacheInterface::class, ArrayCache::class); $container->bind(LoggerInterface::class, FileLogger::class); // Decorators are built left to right, outer decorators are listed first. $container->bindDecorators(PostEndpoint::class, [ LoggingEndpoint::class, CachingEndpoint::class, BaseEndpoint::class ], ['register'], true);
在这个示例中,将在构建后调用《BaseEndpoint》的《register》方法,然后在构建后调用《CachingEndpoint》实例的《register》方法,最后在构建后调用《LoggingEndpoint》实例的《register》方法。
应使用《bind》或《singleton》调用以及一个用于构建装饰器链的闭包来处理装饰器和after-build方法的更复杂和更多组合的绑定。
标记
标签允许将类似实现分组,以便按组引用它们。在需要调用每个实现上的相同方法时,分组实现是有意义的。
use lucatume\DI52\Container; $container = new Container(); $container->bind(UnsupportedEndpoint::class, function($container){ $template = '404'; $message = 'Nope'; $redirectAfter = 3; $redirectTo = $container->get(HomeEndpoint::class); return new UnsupportedEndpoint($template, $message, $redirectAfter, $redirectTo); }); $container->tag([ HomeEndpoint::class, PostEndpoint::class, UnsupportedEndpoint::class, ], 'endpoints'); foreach($container->tagged('endpoints') as $endpoint) { $endpoint->register(); }
《tag》方法支持容器提供的任何绑定对象、闭包、装饰器链和after-build方法的可能性。
回调方法
某些应用程序需要在特定的代码片段中返回回调(或某种形式的可调用对象)。WordPress及其基于事件的架构(例如Filter API)尤其如此。使用容器不会消除这种可能性。
use lucatume\DI52\Container; $container = new Container(); add_filter('some_filter', [$container->get(SomeFilteringClass::class), 'filter']);
此代码存在急切实例化问题:《SomeFilteringClass》被构建是为了绑定它,但可能永远不会使用。这个问题可以通过使用《Container::callback》方法轻松解决。
use lucatume\DI52\Container; $container = new Container(); $container->singleton(SomeFilteringClass::class); add_filter('some_filter', $container->callback(SomeFilteringClass::class, 'filter'));
这种解决方案的优势是,如果调用的是单例,容器每次调用时都会返回相同的回调,并传递相同的参数。
// Some code later we need to remove the filter: we'll get the same callback. remove_filter('some_filter', App::callback(SomeFilteringClass::class, 'filter'));
服务提供者
为了避免传递容器实例(参见 服务定位器模式)或将其全局化,所有的绑定都应在同一PHP文件中完成:随着应用程序的增长,这可能导致代码行数达到数千行。为了避免这种情况,容器支持服务提供者:这些是扩展了 lucatume\DI52\ServiceProvider
类的类,允许将绑定注册组织成逻辑的、自包含且可管理的单元
use lucatume\DI52\ServiceProvider; // file ProviderOne.php class ProviderOne extends ServiceProvider { public function register() { $this->container->bind(InterfaceOne::class, ClassOne::class); $this->container->bind(InterfaceTwo::class, ClassTwo::class); $this->container->singleton(InterfaceThree::class, ClassThree::class); } } // Application bootstrap file. use lucatume\DI52\Container; $container = new Container(); $container->register(ProviderOne::class); $container->register(ProviderTwo::class); $container->register(ProviderThree::class); $container->register(ProviderFour::class);
启动服务提供者
容器实现了一个 boot
方法,该方法会依次调用任何重载了它的服务提供者的 boot
方法。某些应用程序可能在 "boot" 时刻定义常量和环境变量(例如 WordPress 的 plugins_loaded
动作),这可能会使立即注册变得毫无意义。在这种情况下,服务提供者可以重载 boot
方法
// file ProviderOne.php use lucatume\DI52\ServiceProvider; class ProviderOne extends ServiceProvider { public function register() { $this->container->bind(InterfaceOne::class, ClassOne::class); $this->container->bind(InterfaceTwo::class, ClassTwo::class); $this->container->singleton(InterfaceThree::class, ClassThree::class); } public function boot() { if(defined('SOME_CONSTANT')) { $this->container->bind(InterfaceFour::class, ClassFour::class); } else { $this->container->bind(InterfaceFour::class, AnotherClassFour::class); } } } // Application bootstrap file. use lucatume\DI52\Container; $container = new Container(); $container->register(ProviderOne::class); $container->register(ProviderTwo::class); $container->register(ProviderThree::class); // Some code later ... $container->boot();
延迟服务提供者
有时,仅仅设置实现就可能需要如此高昂的前期成本,以至于除非它有必要,否则是不受欢迎的。这种情况可能会发生在需要通过文件杂乱无章地加载(以及旁路加载)以获取简单类实例的非自动加载代码中。为了 "延迟" 这种成本,服务提供者可以重载 deferred
属性和 provides
方法
// file ProviderOne.php use lucatume\DI52\ServiceProvider; class ProviderOne extends ServiceProvider { public $deferred = true; public function provides() { return array(LegacyClassOne::class, LegacyInterfaceTwo::class); } public function register() { include_once('legacy-file-one.php') include_once('legacy-file-two.php') $db = new Db(); $details = $db->getDetails(); $this->container->singleton(LegacyClassOne::class, new LegacyClassOne($details)); $this->container->bind(LegacyInterfaceTwo::class, new LegacyClassTwo($details)); } } // Application bootstrap file use lucatume\DI52\Container; $container = new Container(); // The provider `register` method will not be called immediately... $container->register(ProviderOne::class); // ...it will be called here as it provides the binding of `LegacyClassOne` $legacyOne = $container->get(LegacyClassOne::class); // Will not be called again here, done already. $legacyTwo = $container->get(LegacyInterfaceTwo::class);
使用服务提供者进行依赖注入
容器支持服务提供者的附加依赖注入(版本 3.0.3+)。自动装配将与任何类相同工作,只需重写服务提供者的构造函数并添加任何额外的具体依赖(别忘了调用父类!)
// file ProviderOne.php use lucatume\DI52\ServiceProvider; class ProviderOne extends ServiceProvider { /** * @var ConfigHelper */ protected $config; public function __construct(\lucatume\DI52\Container $container, ConfigHelper $config) { parent::__construct($container); $this->config = $config; } public function register() { $this->container->when(ClassFour::class) ->needs('$value') ->give($this->config->get('value')); } } // Application bootstrap file. use lucatume\DI52\Container; $container = new Container(); $container->register(ProviderOne::class);
如果您想将原始数据注入到服务提供者中,您需要在将提供者注册到容器中之前利用 when
、needs
、give
方法 之前
// file ProviderOne.php use lucatume\DI52\ServiceProvider; class ProviderOne extends ServiceProvider { /** * @var bool */ protected $service_enabled; public function __construct(\lucatume\DI52\Container $container, $service_enabled) { parent::__construct($container); $this->service_enabled = $service_enabled; } public function register() { if (!$this->service_enabled) { return; } $this->container->bind(InterfaceOne::class, ClassOne::class); } } // Application bootstrap file. use lucatume\DI52\Container; $container = new Container(); $container->when(ProviderOne::class) ->needs('$service_enabled') ->give(true); $container->register(ProviderOne::class);
自定义容器
容器将使用一些有偏见的默认值来构建;这些并不是一成不变的,您可以根据需要自定义容器
未绑定类解析
容器将使用反射来确定对象的依赖关系,并且当在 __construct
方法中使用类型提示的对象依赖关系解析对象时,不需要设置。默认情况下,这些 未绑定 类将被解析为 原型,在每次 get
请求时都新建。
要控制解析未绑定类时使用的模式,可以在构建容器时在容器上设置一个标志属性
use lucatume\DI52\Container; $container1 = new Container(); $container2 = new Container(true); // Default resolution of unbound classes is prototype. assert($container1->get(A::class) !== $container1->get(A::class)); // The second container will resolve unbound classes once, then store them as singletons. assert($container2->get(A::class) === $container2->get(A::class));
这只会应用于未绑定类!无论用于构建容器实例的标志是什么,在绑定阶段使用 Container::bind()
或 Container::singleton()
方法设置的设置都将 始终得到尊重。
异常掩盖
默认情况下,容器将捕获在服务解析期间抛出的任何异常,并将其包装在 ContainerException
实例中。
容器将修改异常消息和跟踪文件和行,以提供有关嵌套解析树的有关信息,并将调试引导到导致问题的文件和行。
您可以使用 Container::setExceptionMask()
方法自定义容器处理异常的方式
use lucatume\DI52\Container; $container = new Container(); // The container will throw any exception thrown during a service resolution without any modification. $container->setExceptionMask(Container::EXCEPTION_MASK_NONE); // Wrap any exception thrown during a service resolution in a `ContainerException` instance, modify the message. $container->setExceptionMask(Container::EXCEPTION_MASK_MESSAGE); // Wrap any exception thrown during a service resolution in a `ContainerException` instance, modify the trace file and line. $container->setExceptionMask(Container::EXCEPTION_MASK_FILE_LINE); // You can combine the options, this is the default value. $container->setExceptionMask(Container::EXCEPTION_MASK_MESSAGE | Container::EXCEPTION_MASK_FILE_LINE);