bapcat / phi
一个超级极简的控制反转库
Requires
- php: ^7.4|^8.0
- raphhh/trex-reflection: ^1.1
Requires (Dev)
- bapcat/values: ^1.0
- phpunit/phpunit: ^9.5
- roave/security-advisories: dev-master
This package is auto-updated.
Last update: 2024-09-14 01:09:37 UTC
README
φ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]);
您可以将参数以与构造函数注入相同的方式传递给方法注入。