lucatume/di52

一个兼容 PHP 5.6 的依赖注入容器。

3.3.7 2024-04-26 14:46 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 提供类似于 callbackinstance 的方法,可以轻松集成需要将回调函数连接到事件的事件驱动框架,如 WordPress。
  • 服务提供者 - 为了使您的代码井井有条,该库提供了一个 高级服务提供者实现

目录

代码示例

在应用程序引导文件中,我们定义组件如何组合在一起

<?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_Containertad_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的任何实例都依赖于BC类的实现。"注入"发生在将类A的依赖项传递给它时,"注入"到其构造方法中,而不是在类内部创建。

$a = new A(new B(), new C());

类型提示的灵活性允许向A注入不仅仅是BC的实例,还可以是任何扩展这两个类的类的实例。

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 容器?

BC是接口的具体(即可以实例化)实现,而接口可能永远不会改变,但实现可能会在代码的生命周期中改变:这是依赖倒置原则或"依赖于抽象,非具体"。如果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的实例吗?我将构建BC的实例来构建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);

如果您想将原始数据注入到服务提供者中,您需要在将提供者注册到容器中之前利用 whenneedsgive 方法 之前

// 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);