cora/container

一个关注在整个应用程序中都有用的依赖注入容器,而不仅仅是针对服务。

v1.0.1 2019-08-16 00:16 UTC

This package is auto-updated.

Last update: 2024-09-17 02:37:51 UTC


README

条款

DI = 依赖注入

主要理念

这个库帮助您以理智且易于理解的方式管理应用程序的依赖注入。与仅关注“服务”或使用魔法注入它们(如一些库和框架)不同,此库比现有的任何工具都为您提供更精细的注入过程控制。

使用此工具,您可以为应用程序中的任何类设置注入,甚至包括需要运行时输入的类。依赖注入将递归发生,这可能使手工执行的一系列活动实例化变得轻而易举。通过为您处理所有注入,这也意味着您可以在未来更改类签名和添加新依赖项,而无需修改代码库中的每个实例。

最后,如果您讨厌在代码库中使用工厂类(像我一样),那么请欢呼!您为依赖注入设置应用程序时创建的每个类定义也可以很容易地重用来创建工厂,以便在类内部需要动态对象创建时使用。

基本用法

一切围绕为应用程序定义资源及其创建方式的理念。

简单示例

假设您有一个需要数据库抽象类作为依赖项的以下类

<?php
namespace Classes;

class UserManager {

  protected $db;

  public function __construct($db)
  {
    $this->db = $db;
  }

  public function fetch($id)
  {
    // do stuff
  }
} // end class

为了使容器为您执行依赖注入,您必须定义如何创建该类的一个对象及其所需的任何依赖项

<?php
// Create a container
$container = new \Cora\Container();

// Define some resources on the container
$container->{\Classes\UserManager::class} = function ($c) {
  return new \Classes\UserManager(
    $c->{\Classes\Database::class}()
  );
};

$container->{\Classes\Database::class} = function ($c) {
  return new \Classes\Database();
};

// Grab an instance of UserManager, letting the container handle injection of the database class
$users = $container->{\Classes\UserManager::class}();
$users->fetch($id);

从容器中获取资源格式

注意:在本文档中您将看到的示例中,我们将使用以下格式

$container->{\Classes\SomeClass::class}($arg1, $arg2)

作为从容器中获取事物的方式。或者,如果您更喜欢,您可以使用PSR-11格式的

$container->get(\Classes\SomeClass::class, $arg1, $arg2)

如果资源不存在,两者都会抛出异常。

类内定义(带有更复杂的示例)

在上面的“简单示例”中执行的操作是直接容器定义。这样做的优点是所有内容都在一个地方;然而,这也是缺点。您将遇到的问题是,以这种方式进行资源定义时,定义列表会变得非常长,对于大型应用程序来说。您将不得不进行类名搜索,并怀疑是否有更好的方式来有组织地拆分定义。

正是这个问题促使添加了类内DI定义。类内定义允许您将名为"di_config"的静态方法添加到任何定义如何使用注入实例化的类。这允许您避免长的定义文件问题,同时仍然以分布式的方式组织所有内容,这样您就可以确切地知道在哪里查找任何类的定义(在类文件本身中)。

让我们看看这如何与上述简单示例的更复杂版本一起工作。在这个例子中,我们将看到DI容器在多个级别上进行递归注入。

<?php
// Create a container
$container = new \Cora\Container();

// Define database connection as singleton so it will return the same object every time.
$container->singleton(\Classes\DatabaseConnection::class, function($c) {
  $config = include('/config/database.php');
  return new \Classes\DatabaseConnection($config);
});

// Grab an instance of UserManager, letting the container handle injection of dependencies
$users = $container->{\Classes\UserManager::class}();
$users->fetch($id);

等等,我以为你说这是个更复杂的例子呢???确实是。上面的代码中,$users = $container->{\Classes\UserManager::class}();这一行实际上触发了6个类的实例化和注入连锁反应。UserManager类将同时注入Database和UserRepository类作为依赖,但每个类本身也需要自己的依赖。为了理解发生了什么,请查看下面的类定义,并看看对于在类构造函数中定义的任何依赖,都有一个匹配的di_config()方法,它定义了如何使用注入容器创建对象。如果这还不能澄清问题,我会在下面这些类定义之后,手动写出相同情况的等效代码。

<?php
namespace Classes;

class UserManager {

  protected $db;
  protected $repo;

  public function __construct($db, $userRepository)
  {
    $this->db = $db;
    $this->repo = $userRepository;
  }

  public static function di_config($c)
  {
    return new \Classes\UserManager(
      $c->{\Classes\Database::class}(),
      $c->{\Classes\UserRepository::class}()
    );
  }

  public function fetch($id)
  {
    // do stuff
  }
} // end class
<?php
namespace Classes;

class Database {

  protected $conn;

  public function __construct($connection)
  {
    $this->conn = $connection;
  }

  public static function di_config($c)
  {
    return new \Classes\Database(
      $c->{\Classes\DatabaseConnection::class}()
    );
  }
} // end class
<?php
namespace Classes;

class UserRepository {

  protected $gateway;
  protected $factory;

  public function __construct($gateway, $factory)
  {
    $this->gateway = $gateway;
    $this->factory = $factory;
  }

  public static function di_config($c)
  {
    return new \Classes\UserRepository(
      $c->{\Classes\UserGateway::class}(),
      $c->{\Classes\UserFactory::class}()
    );
  }
} // end class
<?php
namespace Classes;

class UserGateway {

  protected $db;

  public function __construct($db)
  {
    $this->db = $db;
  }

  public static function di_config($c)
  {
    return new \Classes\UserGateway(
      $c->{\Classes\Database::class}()
    );
  }
} // end class
<?php
namespace Classes;

class UserFactory {

  public function __construct()
  {
    // stuff
  }

  public static function di_config($c)
  {
    return new \Classes\UserFactory();
  }
} // end class

手动操作

上面我们提到,$users = $container->{\Classes\UserManager::class}();这一行调用DI容器为我们创建和注入了多个类。使用上面看到的相同的类定义,让我们看看手动做相同工作会是什么样子。

$databaseConfig = include('/config/database.php');
$databaseConnection = new \Classes\DatabaseConnection($databaseConfig);
$users = new \Classes\UserManager(
  new \Classes\Database($databaseConnection),
  new \Classes\UserRepository(
    new \Classes\UserGateway(
      new \Classes\Database($databaseConnection)
    ),
    new \Classes\UserFactory()
  )
);
$users->fetch($id);

类定义的工作方式...

尽管这可能看起来有些神奇,但实际上并非如此。当请求的资源在容器中未明确定义时,容器代码将调用elseif (method_exists($name, 'di_config')) {来检查类是否存在,并且是否有定义一个"di_config"方法。如果存在这样的类和方法,它将使用类定义。非常简单。

对于那些追求完美的人来说,让我们明确一点,这并不是服务定位器模式——你创建对象实例时并没有将容器传递给构造函数。DI配置方法是静态的,只用于保存定义。对象将通过构造函数接收它需要的所有注入依赖。定义紧挨着构造函数函数的能力是方便的,并且肯定比每个类都需要一个单独的定义文件要好得多。

运行时输入

你可以轻松地设置定义来接受运行时输入,如下所示

<?php
namespace Classes;

class User {

  public $name;

  public function __construct($name)
  {
    $this->name = $name;
  }

  public static function di_config($c, $name)
  {
    return new \Classes\User($name);
  }
} // end class
<?php
// Create a container
$container = new \Cora\Container();

$user = $container->{\Classes\User::class}('Bob');
echo $user->name; // Outputs "Bob"

抽象工厂

当你为容器定义如何创建一个对象时,你实际上是在做如果你创建一个类的工厂时你会做的事情。容器和工厂都需要内置一些逻辑来知道如何创建一个对象,并将该对象返回给你。所以如果你已经为容器定义了对象的创建方式,为什么要在任何工厂文件中重复这种逻辑呢?这就是抽象工厂的灵感来源。

抽象工厂是一个通用的包装器,你可以与任何类一起使用,你只需要DI定义。与使用AbstractFactory类构建的工厂相比,最大的区别在于在典型工厂中,make()的调用通常是静态的,如$factory::make()。当使用AbstractFactory时,它不知道要为你创建什么类,直到你实例化它,所以make()的调用必须是非静态的,如下所示:$factory->make()

为了说明你可以如何为注入创建动态工厂,让我们假设你有一个需要创建User对象的UserManager类。正常的解决方案是创建一个UserFactory类,但下面是如何避免这样做的方法。

<?php
namespace Classes;

class UserManager {

  protected $factory;

  public function __construct($userFactory)
  {
    $this->factory = $userFactory
  }

  public static function di_config($c)
  {
    return new \Classes\UserManager(
      $c->getFactory(\Classes\User::class)
    );
  }

  public function create($name)
  {
    return $this->factory->make($name);
  }
} // end class
<?php
namespace Classes;

class User {

  public $name;

  public function __construct($name)
  {
    $this->name = $name;
  }
  
  public static function di_config($c, $name)
  {
    return new \Classes\User($name);
  }
}
// Create a container
$container = new \Cora\Container();
$users = $container->{\Classes\UserManager::class}();
$user = $users->create('John');
echo $user->name; // Outputs "John"

运行测试

如果你有Docker,可以从命令行下载项目并运行docker-compose up。然后从命令行运行./App/phpunit.sh tests/AutoloadTest

文档

有关完整文档,请参阅以下GitHub页面网站:http://joebubna.github.io/Cora/documentation/v2/dependency-injection/overview/

关于Cora

Cora是一组用于快速应用开发的灵活工具。

许可证

Cora框架受MIT许可证许可。