PHP 控制反转(IOC)与依赖注入(DI)

和自甴很熟 提交于 2020-08-18 14:42:29

概念

IOC与DI 据我了解其实早期是JAVA的理念,长期并不被PHP业界接受,因为PHP的理念是简单高效,但是长期的发展使得PHP必须为了工程规范和开发解耦必须走上这条路。Laraval框架为PHP的发展带来了的理念,逐步的各大框架都开始走上了标准化的开发步伐。这其中包含了ThinkPHP、EasySwoole、Swoft等等。

控制反转

传统的开发模式如果我们想要的到一个对象,我们必须去使用new。 这种方式的类控制权限在人的手中,是程序需要的时候认为主动创建依赖的对象(见下图1-1)。

// 图 1-1
// 通常的依赖注入模式
class Course
{
	protected $user;
	public function __construct(User $user) {
		$this->user = $user;
	}
}

依赖注入

前者说控制反转中,系统会将所有的请求单例Bean或者 全局单例Bean 保存在专用的IoC 容器当中,根据代码的需要 选择性的注入对应的需求类,注入的类 由IoC 容器管理, 降低程序的耦合,使得开发人员只需要关注对应的业务逻辑。

两者之间的关联

可以说两者是相辅相成的,依赖注入依靠IOC 控制反转,是IOC的最终目的,而 IOC 对于 依赖注入 是其实现的前提。 这里我们举个栗子 , 其实 PHP的composer 我们可以把它理解为一个IOC 容器, 我们根据程序的需要 动态的加载和删除对应的包文件。而加载的过程可以理解为对应的依赖注入过程。

IOC容器的简单实现

简单的容器接口

<?php
namespace library\Container;
interface ContainerInterface {
	public function get(string $bean);
	public function has(string $bean);
	public function set(string $bean, $value)
}

定义容器基类

<?php
namespace library\Container;
class ContainerAccess implements \ArrayAccess {
	private $keys = [];
	public function __construct(array $value = []) {
		
	}
	public function offsetExists($offset) {
		return isset($this->keys[$offset]);
	}
	public function offsetGet($offset) {
		return $this->keys[$offset];
	}
	public function offsetSet($offset, $value) {
		$this->keys[$offset] = $value; 
	}
	public function offsetUnset($offset) {
		if(isset($this->keys[$offset])) {
			unset($this->keys[$offset])
		}
	}
}

容器类实现

<?php

namespace library\Container;

class Container extends ContainerAccess implements ContainerInterface
{
    /**
     * 存放bean方法
     *
     * @var array
     */
    protected $inject = [];
    /**
     * 存放bean对象
     *
     * @var array
     */
    protected $instance = [];

    /**
     * 获取bean对象
     *
     * @param string $bean
     * @return mixed
     */
    public function get($bean)
    {
        return $this->offsetGet($bean);
    }

    /**
     * 是否存在bean
     *
     * @param [type] $bean
     * @return boolean
     */
    public function has($bean)
    {
        return $this->offsetExists($bean);
    }

    /**
     * 设置bean对象
     *
     * @param string $bean
     * @param string $value
     * @return void
     */
    public function set($bean, $value)
    {
        $this->offset($bean, $value);
    }

    /**
     * 获取bean
     *
     * @param string $bean
     * @return void
     */
    public function __get($bean)
    {
        return $this->offset($bean);
    }

    /**
     * 获取bean
     *
     * @param string $bean
     * @return void
     */
    public function __set($bean, $value)
    {
        $this->offset($bean, $value);
    }

    /**
     * 设置注入方法
     *
     * @param string $bean
     * @param string $methodName
     * @param Closure $methodBody
     * @return void
     */
    public function setInjectMethod($bean, $methodName, $methodBody)
    {
        if (!isset($this->inject[$bean][$methodName])) {
            $this->inject[$bean][$methodName] = $methodBody;
        }
    }

    /**
     * 获取注入方法
     *
     * @param string $bean
     * @param string $methodName
     * @return mixed
     */
    public function getInjectMethod($bean, $methodName)
    {
        if (isset($this->inject[$bean][$methodName])) {
            return $this->inject[$bean][$methodName];
        }
    }

    /**
     * 调用bean方法
     *
     * @param string $bean
     * @param string $methodName
     * @param array $parameters
     * @return void
     */
    public function callback($bean, $methodName, $parameters = [])
    {
        $method = get_class_methods($this->get($bean));
        if (in_array($methodName, $method, TRUE)) {
            call_user_func_array([$this->get($bean), $methodName], $parameters);
        } else {
            $injectMethod = $this->getInjectMethod($bean, $methodName);
            if (isset($injectMethod)) {
                call_user_func($injectMethod, $parameters);
            } else {
                throw new \RuntimeException("$bean 上不存在 $methodName 方法");
            }
        }
    }
}

注入案例

<?php

$user = new model\User;
$user->age = 25;
$user->name = 'xxxx';
$container = new \library\Container\Container();
// 存入容器
$container->set('user', $user);
// 获取userBean
$userBean = $container->user;
// 调用user方法
$userBean->callback('user', 'save');
// 注入类中不存在的方法
$container->setInjectMethod('user', 'methodTest', function () use ($userBean) {
    for ($i = 0; $i < 6; $i++) {
        echo $i;
    }
});
// 调用类不存在的方法
$container->callback('user', 'methodTest');

仿Laravel的依赖注入简单版

<?php

namespace library\Container;

class ClassGenerator extends Container
{
    /**
     * 类构造器
     *
     * @param string $className
     * @return stdClass
     */
    public function build($className)
    {
        if (is_string($className) && isset($this->instance[$className])) {
            return $this->instance[$className];
        }
        // 反射类
        $reflector = new \ReflectionClass($className);
        // 去除不可实例化
        if (!$reflector->isInstantiable()) {
            throw new \RuntimeException("$className can't instantiate");
        }
        // 获取构造
        $construct = $reflector->getConstructor();
        // 无构造
        if (is_null($construct)) {
            return new $className;
        }
        // 有构造
        $parameters = $construct->getParameters();
        $denpendencies = [];
        foreach ($parameters as $parameter) {
            // 获取参数类型
            $denpendency = $parameter->getClass();
            if (is_null($denpendency)) {
                if ($parameter->isDefaultValueAvailable()) {
                    $denpendencies[] = $parameter->getDefaultValue();
                }
            } else {
                // 递归注入
                $denpendencies[] = $this->build($denpendency->name);
            }
        }
        // 实例化类
        $class = $reflector->newInstanceArgs($denpendencies);
        $this->instance[$className] = $class;
        return $class;
    }
}

应用

框架的应用

在HTTP框架中, 依赖注入的应用最为广泛的就是Request对象和Response对象如下图,通常的http执行逻辑都是路由映射控制器方法,框架一般是通过反射来执行控制器函数,而在反射类的同时就可以采用上述类laravel 的方式 获取类型对象,同时可以将Request对象和Response对象直接注入construct构造中,使得用户专心处理业务逻辑。

<?php

namespace app\controller;

class baseController
{
    /**
     * 请求对象
     *
     * @var Request
     */
    private $request;

    /**
     * 返回对象
     *
     * @var Response
     */
    private $response;


    /**
     * 控制基类构造
     *
     * @param Request $request
     * @param Response $response
     */
    public function __construct(Request $request, Response $response)
    {
        $this->request = $request;
        $this->response = $response;
    }
}

类SpringBoot的实现思路

在一个请求当中, 存在请求Bean 和 全局Bean 如果要实现类似springboot的解析注入,只需要在当前的基础上获取类中属性,然后根据属性的注解获取到对应的类名称,注入对应的类。当然在这里 篇幅有限,不做详解,这种注解方式对 php-fpm的意义不大,只有常驻进程的框架有实现的意义。有兴趣的同学可以了解一下 Swoft。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!