依赖注入容器
概述
依赖注入容器 (DIC) 是一个强大的增强功能,它允许您管理应用程序的依赖关系。
理解
依赖注入 (DI) 是现代 PHP 框架中的一个关键概念,用于管理对象的实例化和配置。一些 DIC 库的示例包括:flightphp/container、Dice、Pimple、 PHP-DI 和 league/container。
DIC 是一种花哨的方式,允许您在集中位置创建和管理您的类。这对于需要将同一个对象传递给多个类(例如您的控制器或中间件)时非常有用。
基本用法
以前的方式可能看起来像这样:
require 'vendor/autoload.php';
// 从数据库管理用户的类
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// 在您的 routes.php 文件中
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// 其他 UserController 路由...
Flight::start();从上面的代码中,您可以看到我们创建了一个新的 PDO 对象并将其传递给我们的 UserController 类。对于小型应用程序来说,这没问题,但随着应用程序的增长,您会发现自己在多个地方创建或传递相同的 PDO 对象。这就是 DIC 派上用场的地方。
这里是使用 DIC 的相同示例(使用 Dice):
require 'vendor/autoload.php';
// 与上面相同的类。没有变化
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// 创建一个新的容器
$container = new \Dice\Dice;
// 添加一个规则,告诉容器如何创建 PDO 对象
// 不要忘记像下面一样将其重新赋值给自身!
$container = $container->addRule('PDO', [
// shared 表示每次都会返回相同的对象
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// 这注册了容器处理程序,以便 Flight 知道使用它。
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// 现在我们可以使用容器来创建我们的 UserController
Flight::route('/user/@id', [ UserController::class, 'view' ]);
Flight::start();我猜您可能会想,这个示例添加了很多额外的代码。魔力来自于当您有另一个需要 PDO 对象的控制器时。
// 如果您的所有控制器都有一个需要 PDO 对象的构造函数
// 下面的每个路由都会自动注入它!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);使用 DIC 的额外好处是单元测试变得更容易。您可以创建一个模拟对象并将其传递给您的类。当您为应用程序编写测试时,这是一个巨大的好处!
创建集中式的 DIC 处理程序
您可以通过扩展您的应用程序,在 services 文件中创建一个集中式的 DIC 处理程序。以下是一个示例:
// services.php
// 创建一个新的容器
$container = new \Dice\Dice;
// 不要忘记像下面一样将其重新赋值给自身!
$container = $container->addRule('PDO', [
// shared 表示每次都会返回相同的对象
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// 现在我们可以创建一个可映射的方法来创建任何对象。
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// 这注册了容器处理程序,以便 Flight 知道用于控制器/中间件
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// 假设我们有一个以下示例类,它在构造函数中接受 PDO 对象
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// 发送电子邮件的代码
}
}
// 最后,您可以使用依赖注入创建对象
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();flightphp/container
Flight 有一个插件,它提供了一个简单的符合 PSR-11 的容器,您可以使用它来处理依赖注入。以下是使用它的快速示例:
// 例如 index.php
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// 将正确输出这个!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();flightphp/container 的高级用法
您也可以递归解析依赖关系。以下是一个示例:
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// 实现 ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/DICE
您也可以创建自己的 DIC 处理程序。如果您有一个自定义的容器想要使用它而不是 PSR-11 (Dice),这很有用。请参阅basic usage部分了解如何做。
此外,还有一些有用的默认设置,当使用 Flight 时会让您的生活更容易。
Engine 实例
如果您在控制器/中间件中使用 Engine 实例,以下是配置它的方式:
// 在您的引导文件中某处
$engine = Flight::app();
$container = new \Dice\Dice;
$container = $container->addRule('*', [
'substitutions' => [
// 这就是您传递实例的地方
Engine::class => $engine
]
]);
$engine->registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// 现在您可以在控制器/中间件中使用 Engine 实例
class MyController {
public function __construct(Engine $app) {
$this->app = $app;
}
public function index() {
$this->app->render('index');
}
}添加其他类
如果您有其他想要添加到容器的类,使用 Dice 很容易,因为它们将被容器自动解析。以下是一个示例:
$container = new \Dice\Dice;
// 如果您不需要向您的类注入任何依赖关系
// 您不需要定义任何东西!
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
class MyCustomClass {
public function parseThing() {
return 'thing';
}
}
class UserController {
protected MyCustomClass $MyCustomClass;
public function __construct(MyCustomClass $MyCustomClass) {
$this->MyCustomClass = $MyCustomClass;
}
public function index() {
echo $this->MyCustomClass->parseThing();
}
}
Flight::route('/user', 'UserController->index');PSR-11
Flight 也可以使用任何符合 PSR-11 的容器。这意味着您可以使用任何实现了 PSR-11 接口的容器。以下是使用 League 的 PSR-11 容器的示例:
require 'vendor/autoload.php';
// 与上面相同的 UserController 类
$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::class)
->addArgument('mysql:host=localhost;dbname=test')
->addArgument('user')
->addArgument('pass');
Flight::registerContainerHandler($container);
Flight::route('/user', [ 'UserController', 'view' ]);
Flight::start();这可能比之前的 Dice 示例更冗长,但它仍然能完成相同的好处!
另请参阅
故障排除
- 如果您的容器有问题,请确保您向容器传递正确的类名。
更新日志
- v3.7.0 - 添加了向 Flight 注册 DIC 处理程序的能力。