PHP

php 依赖注入的理解

2019-03-13  本文已影响0人  云龙789

依赖注入是应用于一个类的实例化需要依赖另外一个类的场景。

<?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) 中的第一个 this 是将本闭包函数绑定到当前类(也就是Container上),第二个参数是 bind 函数中,第二个参数 `function (Containercontainer) Container $container` 的实参
如果我们使用回掉的时候不需要传递参数,如以下方式,则 call(),就不需要传递第二个参数
使用 Closure::call 可以得到参数中类的实例化,们以上的测试是为了获取到 Hamburger 类的实例化
但是如果直接使用闭包,则无法获取到想要的类

$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);
    }

}
上一篇下一篇

猜你喜欢

热点阅读