依赖注入容器

介绍

依赖注入容器(DIC)是一个强大的工具,允许您管理应用程序的依赖关系。这是现代 PHP 框架中的一个关键概念,用于管理对象的实例化和配置。一些 DIC 库示例包括: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());
    }
}

$User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass'));
Flight::route('/user/@id', [ $UserController, 'view' ]);

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;
// 不要忘记像下面这样重新分配它给自己!
$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', 'view' ]);
// 或者您还可以像这样定义路由
Flight::route('/user/@id', 'UserController->view');
// 或者
Flight::route('/user/@id', 'UserController::view');

Flight::start();

您可能认为在示例中有很多额外的代码。其中的魔法之处在于当您有另一个需要 PDO 对象的控制器时。


// 如果您的所有控制器都有一个需要 PDO 对象的构造函数
// 下面的每个路由将自动注入它!!!
Flight::route('/company/@id', 'CompanyController->view');
Flight::route('/organization/@id', 'OrganizationController->view');
Flight::route('/category/@id', 'CategoryController->view');
Flight::route('/settings', 'SettingsController->view');

利用 DIC 的额外好处是进行单元测试变得更加简单。您可以创建一个模拟对象并将其传递给您的类。当您为应用程序编写测试时,这是一个巨大的好处!

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 示例相比,这可能会更冗长一些,但仍然可以以相同的好处完成工作!

自定义 DIC 处理程序

您还可以创建自己的 DIC 处理程序。如果您有一个不符合 PSR-11(Dice)的自定义容器,这会很有用。参见 基本示例 了解如何处理。

另外,在使用 Flight 时,还有一些有用的默认设置可以让您更轻松。

引擎实例

如果您在控制器/中间件中使用 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');