概念
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。
来源:oschina
链接:https://my.oschina.net/u/4173863/blog/4314273