美文网首页PHP
php 依赖注入的理解

php 依赖注入的理解

作者: 云龙789 | 来源:发表于2019-03-13 13:44 被阅读0次

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

    <?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 类不变,如果改成以下使用方式
    $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);
        }
    
    }
    

    相关文章

      网友评论

        本文标题:php 依赖注入的理解

        本文链接:https://www.haomeiwen.com/subject/levzpqtx.html