bapcat/phi

一个超级极简的控制反转库

4.0.1 2023-06-13 22:33 UTC

This package is auto-updated.

Last update: 2024-09-14 01:09:37 UTC


README

Build Status Coverage Status License

φhi

φhi是一个高效、易于使用、开源的PHP依赖注入容器,具有小巧的体积、强大的功能、100%的单元测试覆盖率以及出色的文档。φhi兼容PSR-0和PSR-4自动加载标准,并欢迎任何觉得可以改进的人进行合作。

安装

Composer

Composer 是安装BapCat包的推荐方法。

$ composer require bapcat/phi

GitHub

可以从 GitHub 下载BapCat包。

特性

φhi支持多种不同的依赖注入方式,这些方式都可以单独使用或相互结合。

自动注入

假设你有一个名为 Foo 的类,它依赖于另一个名为 Bar 的类

class Foo {
  public $bar = null;
  
  public function __construct(Bar $bar) {
    $this->bar = $bar;
  }
}

你可以通过以下方式轻松获取带有所有必需依赖项的新 Foo 实例

$foo = $phi->make(Foo::class);
// $foo->bar = new Bar

你会得到一个新实例的 Foo,其中自动注入了新的 Bar 实例到构造函数中。这当然可以递归工作。如果 Bar 依赖于 Baz,则会将 Baz 的实例注入到 Bar 中,依此类推。

传递参数

将会有很多需要将参数传递到构造函数中的情况。考虑以下类(注意参数的顺序)

class Foo {
  public $a;
  public $b;
  public $first_name;
  public $last_name;
  
  public function __construct(B $b, A $a, $first_name = null, $last_name = null) {
    $this->a = $a;
    $this->b = $b;
    $this->first_name = $first_name;
    $this->last_name  = $last_name;
  }
}

你可以通过以下几种方式从φhi请求此类

$foo = $phi->make(Foo::class);
// $foo->a == new A
// $foo->b == new B
// $foo->first_name == null
// $foo->last_name  == null
$foo = $phi->make(Foo::class, ['John', 'Doe']);
// $foo->a == new A
// $foo->b == new B
// $foo->first_name == 'John'
// $foo->last_name  == 'Doe'
$foo = $phi->make(Foo::class, ['John']);
// $foo->a == new A
// $foo->b == new B
// $foo->first_name == 'John'
// $foo->last_name  == null

你可能想要覆盖自动注入的参数

$a = new A;
$foo = $phi->make(Foo::class, ['John', 'Doe', $a]);
// $foo->a == $a
// $foo->b == new B
// $foo->first_name == 'John'
// $foo->last_name  == 'Doe'

请注意,在先前的例子中 $a 是最后一个传递的。φhi足够聪明,能够计算出非标量类型参数的注入顺序。

相同类型的多个依赖项

考虑以下类

class Foo {
  public function __construct(BarInterface $bar, A $a, BarInterface $baz, B $b) {
    // ...
  }
}
$bar = new Bar; // implements BarInterface
$baz = new Baz; // implements BarInterface
$foo = $phi->make(Foo::class, [$bar, $baz]);
// $foo->bar == $bar
// $foo->baz == $baz
// $foo->a   == new A
// $foo->b   == new B

相同类型的参数将按照它们提供给φhi的顺序传递到构造函数中。如果您想以不同的顺序传递它们,请参阅命名注入部分。

命名注入

在某些情况下,明确指定传递的参数是有用的。φhi使得这变得简单。考虑“传递参数”部分中的类

class Foo {
  public function __construct(A $a, B $b, $first_name = null, $last_name = null) {
    // ...
  }
}
$a = new A;
$b = new B;
$foo = $phi->make(Foo::class, [$b, 'last_name' => 'Doe', $a]);
// $foo->a == $a
// $foo->b == $b
// $foo->first_name == null
// $foo->last_name  == 'Doe'

绑定

许多现代应用程序都有可能被替换的部分。这是通过使用接口来实现的。φhi允许使用绑定自动注入接口

interface BarInterface {

}

class Bar implements BarInterface {

}

class Foo {
  public function __construct(BarInterface $bar) {
    // ...
  }
}

$phi->bind(BarInterface::class, Bar::class);
$foo = $phi->make(Foo::class);
// $foo->bar == new Bar
$bar = $phi->make(BarInterface::class);
// $bar = new Bar

绑定甚至允许你用另一个具体实例替换一个类的实例

$phi->bind(A::class, B::class);

$a = $phi->make(A::class);
// $a == new B

带参数的依赖项

有时你可能有一个需要参数的依赖项。这可以通过将一个类绑定到一个可调用对象上来完成

$phi->bind(Person::class, function() {
  return new Person('John', 'Doe');
});

$person = $phi->make(Person::class);
// $person->first_name == 'John'
// $person->last_name  == 'Doe'

这在你需要执行实例化类的逻辑时也很有用

$id = 0;

$phi->bind(Person::class, function() use(&$id) {
  return new Person(++$id);
});

$person1 = $phi->make(Person::class); // id == 1
$person2 = $phi->make(Person::class); // id == 2

传递给φhi的任何参数都将直接传递给可调用对象

$id = 0;

$phi->bind(Person::class, function($name, $age) use(&$id) {
  $names = explode(' ', $name);
  return new Person(++$id, $name[0], $name[1], $age);
});

$person = $phi->make(Person::class, ['John Doe', 21]);
// $person->id == 1
// $person->first_name == 'John'
// $person->last_name  == 'Doe'
// $person->age == 21

单例

φhi还允许将绑定到类的实际实例。这可以用来创建单例

$default_pdo = new PDO(...); // The default database
$stats_pdo   = new PDO(...); // PDO pointing to a different database

$phi->bind(PDO::class, $default_pdo);
$pdo = $phi->make(PDO::class);
// $pdo == $default_pdo
class Table {
  public function __construct(PDO $pdo, $table_name) {
    // ...
  }
}

$users_table = $phi->make(Table::class, ['users']);
// $users_table->pdo   == $default_pdo
// $users_table->table == 'users'

$stats_table = $phi->make(Table::class, [$stats_pdo, 'stats']);
// $stats_table->pdo   == $stats_pdo
// $stats_table->table == 'stats'

别名

有时,代码库中会有许多常用类,但名称难以记忆。例如,Vendor\Package\Core\Logging\Log。给这样的类起一个更短、更容易输入的名字可能很有用

$phi->bind('core.log', \Vendor\Package\Core\Logging\Log::class);

$log = $phi->make('core.log');
// $log == new Vendor\Package\Core\Logging\Log

你也可以将别名绑定到可调用对象或单例。

自定义解析器

有时您可能需要匹配多个别名,而不是单个别名。自定义解析器就是为了这个目的而设计的。当从Phi请求绑定时,将依次执行已注册的任何自定义解析器,按添加的顺序执行,第一个返回非空值的解析器将被使用。如果所有自定义解析器都返回null,Phi将正常解析绑定。

use BapCat\Interfaces\Ioc\Resolver;

class CustomResolver implements Resolver {
  public function make($alias, array $arguments = []) {
    if($alias == A::class) {
      return new B(new A());
    }
  }
}

$phi->addResolver(new CustomResolver());
$b = $phi->make(A::class);
//$b == new B

使用自定义解析器的另一个原因是包装其他IoC容器。例如,如果您正在使用Laravel,可以将Laravel容器与Phi结合使用

use Illuminate\Support\Facades\App;
use BapCat\Interfaces\Ioc\Resolver;

class LaravelResolver implements Resolver {
  public function make($alias, array $arguments = []) {
    if(App::bound($alias)) {
      return App::make($alias, $arguments);
    }
  }
}

$phi->addResolver(new LaravelResolver());

这样,注册在Laravel IoC容器中的任何绑定都将由它解析。其余的将传递给Phi。

递归解析

可以将一个绑定绑定到另一个绑定上。

$ioc->bind(FooInterface::class, Foo::class);
$ioc->bind('bap.foo', FooInterface::class);

### Singletons
While it's possible to bind an alias to an instance of a class, effectively creating a singleton, it's not always
desirable (or possible) to load all singletons at boot time.  Instead, singletons can be registered and lazy-loaded.

```php
$phi->singleton(FooInterface::class, Foo::class);

第一次请求FooInterface的实例时,将加载并绑定Foo。从那时起,所有对FooInterface的请求都将返回相同的Foo实例。

可调用依赖注入

依赖注入不仅适用于创建对象时,在调用方法时也可能很有用。Phi可以对任何PHP接受的作为callable类型提示的方法执行依赖注入。

$phi->call([$instance, 'method']);
$phi->call([Class::class, 'staticMethod']);
$phi->call(['Class::staticMethod']);
$phi->call($invokableClass);
$phi->call(function(FooInterface $foo) { });
$phi->call('var_dump', [$foo]);

您可以将参数以与构造函数注入相同的方式传递给方法注入。