vanilla / garden-container
一个依赖注入容器。
Requires
- php: >=7.4
- psr/container: *
Requires (Dev)
- ext-xdebug: *
- phpunit/phpunit: ~8.0
- vimeo/psalm: ^4.9
- dev-master
- v4.1.1
- v4.1.0
- V4.0.1
- v4.0.0
- v4.0.0-beta1
- v3.1.0
- v3.0.7
- v3.0.6
- v3.0.5
- v3.0.4
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.0.2
- v2.0.1
- v2.0
- v1.5.1
- v1.5
- v1.4
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.0
- v0.1.0
- dev-fix/php-7.4
- dev-feature/protected-create-instance
- dev-feature/prettier
- dev-remove-psr-log-composer-lock
- dev-feature/php8-Upgrade
- dev-feature/VNLA-2728-garden-container-support-for-php8-and-unionTypes
- dev-feature/VNLA-2727-make-home-page-render-with-php8
- dev-refactor/container-config-interface
- dev-feature/container-configuration-interface
- dev-tweak/not-found-arg-exception-message
- dev-add/php-74
- dev-add/php-8
- dev-feature/generic-type-annotation
- dev-test/circular-references
- dev-tweak/badges
- dev-feature/php-71-optional-param
- dev-fix/optional-params
- dev-hotfix/null-args
- dev-release/1.x
This package is auto-updated.
Last update: 2024-09-15 19:00:22 UTC
README
花园容器是一个简单但功能强大的依赖注入容器。
特点
- 通过参数类型提示自动连接依赖项。在您进行任何配置之前,您就可以获得很多功能。
- 无需使用难以测试的静态变量即可创建对象的共享实例。
- 可以为基类和接口配置依赖项,并在子类之间共享。
- 可以为容器中的类配置设置器注入。
- 可以配置依赖项以引用子容器。使用容器将配置文件中的属性注入到您的应用程序中。
- 可以更改实现依赖项的类或指定接口的最终类。
- 可以使用自定义工厂函数来构建对象,以处理重构或边缘情况。
依赖注入的基本原理
考虑以下简单的对象结构,其中控制器对象依赖于模型对象,而该模型对象依赖于数据库连接。
class Controller { public function __construct(Model $model) { } } class Model { public function __construct(PDO $db) { } }
为了使用控制器,您需要进行相当多的构建。
$controller = new Controller(new Model(new PDO($dsn, $username, $password);
您可以看到,当您必须创建许多对象或深层对象层次结构时,这会变得多么混乱。使用依赖注入容器,您不必做任何这些。
$dic = new Container(); $controller = $dic->get("Controller"); // dependencies magically wired up
容器检查其构建的对象的类型提示,然后通过递归回容器来构建这些对象。这称为“自动连接”,允许您以非常简单的方式创建任意数量的复杂对象图。如果您稍后想添加更多依赖项,只需向构造函数添加一个参数即可,它将自动解决。
设计良好的应用程序将严重依赖于自动连接,并且只为少数依赖项配置容器。
使用规则配置容器
您可以使用规则覆盖任何类的实例化行为。要为类配置规则,您使用rule()
方法选择规则,然后使用任何各种规则获取器和设置器。
命名空间
规则通常使用您将从容器中获取的类的名称命名。如果您使用命名空间,则规则必须使用类的完全限定名命名。名称可以以正斜杠开头,但在处理之前将被删除。
PHP 5.6 引入了 ::class
构造,这是为容器指定类名的一种有用方式。
大小写敏感性
容器应被视为大小写敏感的,但是如果您尝试获取具有不正确大小写的类,则容器可以找到该类,如果该类已经包含或自动加载器是大小写不敏感的。由于大多数PSR自动加载器是大小写敏感的,因此如果容器中大小写不严谨,您可能会遇到错误。
构造函数参数
自动连接仅适用于类型提示参数,但如果一个类有其他参数,您将必须使用setConstructorArgs()
方法进行配置。
$dic = new Container(); $dic->rule("PDO")->setConsructorArgs([$dsn, $username, $password]);
在此,新的PDO实例将配置正确的凭据。这种做法的一个好处是,容器仅在从容器检索新对象时才传递配置。
混合类型提示和非类型提示的构造函数参数
如果一个类有一些类型提示和一些常规参数,你只需要用构造函数参数指定非类型提示的参数。其他参数将由容器自动装配。
class Job { public function __construct(Envornment $env, $name, Logger $log) { } } $dic = new Container(); $dic->rule("Job")->setConstructorArgs(["job name"]); $job = $dic->get("Job");
命名参数
当你向容器的方法传递参数数组时,你可以使用数组键来匹配特定的参数名称。如果你想指定参数列表中的特定参数,这很有用。你也可以通过指定名称来覆盖类型提示的参数。
$dic->rule("Job")->setConstructorArgs([ "name" => "job name", "log" => $dic->get("SysLogger"), ]);
在对象创建时传递构造函数参数
你可以使用 getArgs()
来传递一些或所有构造函数参数。
$dic = new Container(); $pdo = $dic->getArgs("PDO", [$dsn, $username, $password]);
共享对象
将类标记为共享意味着容器在每次请求该类时都会返回相同的实例。这比全局变量或单例要好得多。
$dic = new Container(); $dic->rule("PDO") ->setConsructorArgs([$dsn, $username, $password]) ->setShared(true); $db1 = $dic->get("PDO"); $db2 = $dic->get("PDO"); // $db1 === $db2
使用调用进行设置注入
你可以在规则中添加方法调用。每个添加的调用在对象首先创建后将按顺序调用。调用与构造函数以类似的方式工作,因此如果存在类型提示参数,它们也会自动装配。
$dic = new Container(); $dic->rule("PDO") ->setConsructorArgs([$dsn, $username, $password]) ->addCall("setAttribute", [PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION]) ->addCall("setAttribute", [PDO::MYSQL_ATTR_INIT_COMMAND, "set names utf8"]);
指定规则的类
你可以使用 setClass()
方法指定从容器获取项目时创建的类。当你想指定抽象基类或接口的特定子类来满足依赖项时,这很有用。规则也不必代表实际的类;在这种情况下,你必须指定类。
$dic = new Container(); $dic->rule("Psr\Log\LoggerInterface")->setClass("SysLogger");
规则继承
默认情况下,所有子类都会从其基类继承规则。通过这种方式,你可以只为基类定义规则。如果你不想让子类继承规则,则可以通过 setInherit()
覆盖此行为。
class Model { ... } class UserModel extends Model { ... } $dic->rule('Model') ->setShared(true); $um1 = $dic->get('UserModel'); $um2 = $dic->get('UserModel'); // $um1 === $um2
接口继承
规则可以以有限的方式从接口继承。如果你在接口上定义规则,任何实现它的类都会调用其方法调用,并且如果它们没有定义自己的,还会使用接口规则构造函数的参数。
$dic->rule("Psr\Log\LogAwareInterface")->addCall("setLogger");
默认规则
有一个默认规则,规则会从中继承。你可以通过选择 defaultRule()
方法或 rule('*')
来修改此规则。
$dic->defaultRule()->setShared(true); // Now all objects are shared by default.
引用依赖项
你可以指定引用容器内部的参数。为此,你指定参数为 Reference
对象。你使用一个数组来构造引用对象,其中每个项目都是容器或子容器的键。
class Config { public function __construct($path) { $this->data = json_decode(file_get_contents($path), true); } public function get($key) { return $this->data[$key]; } } $dic = new Container(); $dic->rule(Config::class) ->setShared(true) ->setConstructorArgs(["../config.json"]) ->rule(PDO::class) ->setConstructorArgs([ new Reference([Config::class, "dsn"]), new Reference([Config::class, "user"]), new Reference([Config::class, "password"]), ]); $pdo = $dic->get(PDO::class);
在上面的例子中,PDO 对象将使用容器中的 Config 对象提供的信息进行构造。每个引用首先指定 Config::class
,因此容器首先查找该对象,然后调用 get()
并传入引用数组中的下一个项目。
ReferenceInterface
Garden\Container
命名空间定义了一个 ReferenceInterface
,你可以实现它以使用自定义引用来满足依赖项。还有一个可以用来满足具有可调用参数的引用的 Callback
类。
在容器中设置特定实例
你可以使用 setInstance()
方法将特定对象实例设置到容器中。当你这样做时,该对象始终是共享的。setInstance 的一种用途是将容器放入自身,以便它可以作为依赖项。这被认为是一种反模式,但在某些情况下可能是必要的。
class Dispatcher public function __construct(Container $dic) { $this->dic = $dic; } public function dispatch($url) { $args = explode('/', $url); $controllerName = ucfirst(array_shift($args)).'Controller'; $method = array_shift($args) ?: 'index'; $controller = $this->dic->get($controllerName); return $this->dic->call([$controller, $method], $args) } } $dic = new Container(); $dic->setInstance(Container::class, $dic); $dispatcher = $dic->get(Dispatcher::class); $dispatcher->dispatch(...);
call()
方法与 call_user_func_array 类似,但它是通过容器调用的,因此依赖项会像其他方法一样自动装配。
别名
你可以指定一个规则是另一个规则的别名。在别名上调用 get() 与调用被别名的规则上的 get() 相同。以下方法用于定义别名。
-
getAliasOf(),setAliasOf()。这些方法将使当前规则别名为另一个规则。注意,别名的规则会忽略其他设置,因为它们是从目标规则中获取的。
-
addAlias(),removeAlias(),getAliases()。这些方法将为当前规则添加别名。这些方法通常更方便,因为您通常希望同时配置规则和设置别名。
为什么使用别名?
当您在基类、类或接口之间存在不一致的类型提示依赖关系时,别名非常有用,并且您希望它们都解析到相同的共享实例。
class Task { public function __construct(LoggerInterface $log) { } } class Item { public function __construct(AbstractLogger $log) { } } $dic = new Container(); $dic->rule(LoggerInterface::class) ->setClass("SysLogger") ->setShared(true) ->addAlias(AbstractLogger::class); $task = $dic->get(Task::class); $item = $dic->get(Item::class); // Both logs will point to the same shared instance.
致谢
本项目深受出色的 DICE 项目以及程度较小的 Aura.Di 项目的启发。Garden Container 中的任何类似于这些项目的代码很可能来自它们,并且仍然是各自所有者的版权。这些项目的开发者比我们聪明得多。