php 依赖注入的理解
依赖注入是应用于一个类的实例化需要依赖另外一个类的场景。
<?php
class Bread
{
}
class Hamburger
{
protected $materials;
public function __construct(Bread $bread)
{
$this->materials = [$bread];
}
/**
* 测试函数
*/
public function getName()
{
echo '111';
}
}
class Container
{
/**
* @var Closure[]
*/
public $binds = [];
/**
* Bind class by closure.
*
* @param string $class
* @param Closure $closure
* @return $this
*/
public function bind(string $class, Closure $closure)
{
$this->binds[$class] = $closure;
return $this;
}
/**
* Get object by class
*
* @param string $class
* @param array $params
* @return object
*/
public function make(string $class, array $params = [])
{
if (isset($this->binds[$class])) {
// 或者 if (array_key_exists($class,$this->binds)) {
return ($this->binds[$class])->call($this, $this, ...$params);
}
return new $class(...$params);
}
}
$container = new Container();
$container->bind(Hamburger::class, function (Container $container) {
// 此时使用 make 得到i的是 return new $class(...$params); 也就是返回的 Bread 类的实例,因为没有 bind Bread 这个类
$bread = $container->make(Bread::class);
return new Hamburger($bread);
});
//
echo '<pre>';
echo '$container类';
print_r($container);
echo '<hr>';
echo 'Hamburger参数值<br>';
print_r(($container->binds['Hamburger'])->call($container, $container)); // 直接使用类名或者类都可以
echo '或者<br>';
print_r(($container->binds[Hamburger::class])->call($container, $container));
echo '<hr>';
echo 'Hamburger 的闭包 <br>';
print_r(($container->binds[Hamburger::class]));
image.png
在调用 bind 方法的时候,第一个参数并不一点非要传递类,是可以传递任何字符串的,只要保证 bind(‘string’) 与 make('string') 中 string 值保持一致即可,这也是laravel 中 alias别名的原理。 别传递类,是为了在使用的时候方便知道make 出来的是哪个类。这可以说是一种约定
以上方式,为了好理解,我直接取出了 make中的 return 处理,在实例使用中,如果需要使用某个类,在绑定之后,可以直接使用以下方式就可以获取到类的实例化
$hambuger = $container->make(Hamburger::class);
$container->binds[Hamburger::class]
对应的值是一个闭包。因为 Hamburger::class 在使用到 bind 方法的时候,调用第二个参数 Closure 的时候是需要传递值的,所以($this->binds[$class])->call($this, $this, ...$params)
中的第一个 container)中
Container $container` 的实参
如果我们使用回掉的时候不需要传递参数,如以下方式,则 call(),就不需要传递第二个参数
使用 Closure::call 可以得到参数中类的实例化,们以上的测试是为了获取到 Hamburger 类的实例化
但是如果直接使用闭包,则无法获取到想要的类
- Container 类不变,如果改成以下使用方式
$container = new Container();
$container->bind(Hamburger::class, function () {
return 111;
});
echo '<pre>';
print_r(($container->binds[Hamburger::class])->call($container));
echo '<hr>';
print_r(($container->binds[Hamburger::class])());
make 方法也可以改成
public function make(string $class, array $params = [])
{
if (isset($this->binds[$class])) {
return ($this->binds[$class])->call($this, ...$params);
}
return new $class(...$params);
}
image.png
不过如开篇所说,依赖注入是应用于一个类的实例化需要依赖另外一个类的场景
如果一个类的实例化不需要依赖别的类,那么我们就不需要使用 Container 类的这种方式了
以上方式的依赖注入,需要先使用 bind 函数绑定相应的类,并在绑定的回掉中将构造函数需要的类 Bread 先实例化,这样其实并没有得到很好的优化。我们可以使用PHP的反射机制,来自动的实例化需要的参数类,而不用手动 bind
/**
* @param string $class
* @param array $params
* @return mixed|object
* @throws ReflectionException
*/
public function make(string $class, array $params = [])
{
if (isset($this->binds[$class])) {
return ($this->binds[$class])->call($this, $this, ...$params);
}
return $this->binds[$class] = $this->resolve($class, $params);
}
/**
* Get object by reflection
*
* @param $abstract
* @return object
* @throws ReflectionException
*/
protected function resolve($abstract)
{
// 获取反射对象
$constructor = (new ReflectionClass($abstract))->getConstructor();
// 构造函数未定义,直接实例化对象
if (is_null($constructor)) {
// 如果没有构造函数,则直接返回实 $abstract 的例化
return new $abstract;
}
// 获取构造函数参数
$parameters = $constructor->getParameters();
$arguments = [];
foreach ($parameters as $parameter) {
// 获得参数的类型提示类
$paramClassName = $parameter->getClass()->getName();
// 参数没有类型提示类,抛出异常
if (is_null($paramClassName)) {
throw new Exception('Fail to get instance by reflection');
}
// 实例化参数 $paramClassName 参数没有绑定的情况下,还是会回掉到 resolve 函数,但是总会有不需要类实例为参数的类,为节点,则一步步走出来。
// 但是这种情况下是使用与实例传递的是类的情况,而不适用于其他参数 如果在 Bread 中添加一个构造函数,你就会发现报错
//不过 $constructor = (new ReflectionClass($abstract))->getConstructor(); 就是限制的查看构造函数的处理。
$arguments[] = $this->make($paramClassName);
}
return new $abstract(...$arguments);
}
使用
$container = new Container();
$hambuger = $container->make(Hamburger::class);
$hambuger->getName();
<?php
namespace Core;
/**
* 容器文件
* Class Container
* @package Core
*/
class Container
{
protected $binds = [];
/**
* 获取一个类的实例化
* @param $class
* @param array $params
* @return mixed
* @throws \ReflectionException
*/
public function make($class, array $params = [])
{
if (isset($this->binds[$class])) {
return ($this->binds[$class])->call($this, $this, ...$params);
}
return $this->binds[$class] = $this->resolve($class, $params);
}
/**
* 解析一个类的构造函数,并且实例化
* @param $abstract
* @param $request_params
* @return mixed
* @throws \ReflectionException
*/
protected function resolve($abstract, $request_params)
{
// 获取构造函数
$constructor = (new \ReflectionClass($abstract))->getConstructor();
// 没有构造函数,则直接返回类的实例化
if (is_null($constructor)) {
return new $abstract;
}
// 获取构造函数中的参数
$params = $constructor->getParameters();
$arguments = [];
foreach ($params as $param) {
if (is_null($param->getClass())) {
$arguments[] = $request_params[$param->getPosition()]; // 对应的位置赋值对应的参数
// 参数不是类
} else {
$arguments[] = $this->make($param->getClass()->getName());
}
}
return new $abstract(...$arguments);
}
}