文档章节

PHP实现依赖注入

o0无忧亦无怖
 o0无忧亦无怖
发布于 2017/08/10 22:11
字数 1169
阅读 2343
收藏 109
点赞 8
评论 38

高层模块不应该依赖于底层模块,两个都应该依赖抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

###首先,我们来看一段代码:

class A{
        public function echo()
        {
                echo 'A'.PHP_EOL;
        }
}
class EchoT {
        protected  $t;
        public function __construct()
        {
              $this->t = new A();
        }
        public function echo(){
                $this->t->echo();
        }
}

初始,我们都使用new 的方式在内部进行,EchoT类严重依赖于类A。每当类A变化时,EchoT类也得进行变化。 ###我们优化一下代码

class EchoT {
        protected  $t;
        public function __construct($t)  //构造器注入由构造器注入到其中
        {
              $this->t = $t;
        }

可以看到,这样做的话。很大程序上,我们对程序进行了解耦。类A无论你如何变动,EchoT类是不需要变动的。不再依赖于A。但是新问题又来了,我们现在只有A,万一来了B,来了CDEFG怎么办。 ###面向接口

interface T{
        public function echo();
}

class A{
        public function echo()
        {
                echo 'A'.PHP_EOL;
        }
}

class B implements T{
        public function echo()
        {
                echo 'B'.PHP_EOL;
        }
}
class EchoT {
        protected  $t;
        public function __construct(T $t)  //构造器注入由构造器注入到其中
        {
              $this->t = $t;
        }
        public function echo(){
                $this->t->echo();
        }
}

将T抽象出为接口,这样,EchoT类中的echo方法变成一个抽象的方法,不到运行那一刻,不知道他们的Method方式是怎么实现的。 ###工厂

function getT($str) {
    if(class_exists($str)){
        return new $str();
        }
}

T要使用哪个是不明确的,因此,我们可以将其工厂化。【看上去很简单,在DI实际上有体现】

###DI(重点来了) 首先,我们看一下PHP的psr规范。

http://www.php-fig.org/psr/psr-11/

官方定义的接口

Psr\Container\ContainerInterface
包含两个方法
function get($id);
function has($id);

仔细看上面的工厂,是不是和get($id)很一致,PHP官方将其定义为容器(Container,我个人理解,就是一个复杂的工厂)

dependency injection container

依赖注入容器

namespace Core;
use Psr\Container\ContainerInterface;
class Container implements ContainerInterface
{
        protected $instance = [];//对象存储的数组
        public function __construct($path) {
                $this->_autoload($path);  //首先我们要自动加载  psr-autoload
        }

        public function build($className)
        {
                if(is_string($className) and $this->has($className)) {
                        return $this->get($className);
                }
                //反射
                $reflector = new \ReflectionClass($className);
                if (!$reflector->isInstantiable()) {
                        throw new \Exception("Can't instantiate ".$className);
                }
                // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
                if (!$reflector->isInstantiable()) {
                        throw new \Exception("Can't instantiate ".$className);
                }
                /** @var \ReflectionMethod $constructor 获取类的构造函数 */
                $constructor = $reflector->getConstructor();
                // 若无构造函数,直接实例化并返回
                if (is_null($constructor)) {
                        return new $className;
                }
                // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
                $parameters = $constructor->getParameters();
                // 递归解析构造函数的参数
                $dependencies = $this->getDependencies($parameters);
                // 创建一个类的新实例,给出的参数将传递到类的构造函数。
                $class =  $reflector->newInstanceArgs($dependencies);
                $this->instance[$className] = $class;
                return $class;
        }

        /**
         * @param array $parameters
         * @return array
         */
        public function getDependencies(array $parameters)
        {
                $dependencies = [];
                /** @var \ReflectionParameter $parameter */
                foreach ($parameters as $parameter) {
                        /** @var \ReflectionClass $dependency */
                        $dependency = $parameter->getClass();
                        if (is_null($dependency)) {
                                // 是变量,有默认值则设置默认值
                                $dependencies[] = $this->resolveNonClass($parameter);
                        } else {
                                // 是一个类,递归解析
                                $dependencies[] = $this->build($dependency->name);
                        }
                }
                return $dependencies;
        }

        /**
         * @param \ReflectionParameter $parameter
         * @return mixed
         * @throws \Exception
         */
        public function resolveNonClass(\ReflectionParameter $parameter)
        {
                // 有默认值则返回默认值
                if ($parameter->isDefaultValueAvailable()) {
                        return $parameter->getDefaultValue();
                }
                throw new \Exception($parameter->getName().' must be not null');
        }
        /**
         * 参照psr-autoload规范
         * @param $path
         */
        public function _autoload($path) {
                spl_autoload_register(function(string $class) use ($path) {
                        $file = DIRECTORY_SEPARATOR.str_replace('\\',DIRECTORY_SEPARATOR, $class).'.php';
                        if(is_file($path.$file)) {
                                include($path.$file);
                                return true;
                        }
                        return false;
                });
        }

        public function get($id)
        {
                if($this->has($id)) {
                        return $this->instance[$id];
                }
                if(class_exists($id)){
                        return $this->build($id);
                }
                throw new ClassNotFoundException('class not found');  //实现的PSR规范的异常
        }

        public function has($id)
        {
                return isset($this->instance[$id]) ? true : false;
        }
}

####使用示例

$container = new Container('../');//假设这是路径
$echoT = $container->get(\Test\EchoT::class);     //假设echoT类的命名空间是\Test
$echoT->echo();

这个时候,会出现一个问题:

  // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
                if (!$reflector->isInstantiable()) {
                        throw new \Exception("Can't instantiate ".$className);
                }
因为接口T是无法实例化的,所以,一般在程序内,我们都加上别名(参照laravel框架)
$container->alisa(\Test\T::class,\Test\T\A::class);  //指定接口T使用类A(控制反转)

####针对接口 下面是alias方法

      public function alias(string $key, $class, bool $singleton = true) 
        {
                if($singleton) {
                        $this->singleton[] = $class;
                }
                $this->aliases[$key] = $class;
                return $this;
        }
    //同时,我们需要在build的时候进行判断是否为别名
 public function build($className)
        {
                if(is_string($className) and $this->has($className)) {
                        return $this->get($className);
                }
                if(isset($this->aliases[$className])) {
                        if(is_object($this->aliases[$className])) {
                               return $this->aliases[$className];
                        }
                        $className = $this->aliases[$className];
                }

就此,一个简单的PHP容器就实现了。 ###个人实现代码

我最近一个爬虫项目(基于swoole)

###参考:

PHP之道

编程老头

PHP程序员如何理解依赖注入容器(dependency injection container)

© 著作权归作者所有

共有 人打赏支持
o0无忧亦无怖
粉丝 33
博文 74
码字总数 49253
作品 1
程序员
加载中

评论(38)

o0无忧亦无怖
o0无忧亦无怖

引用来自“VOYAGE0”的评论

PHP 这些依赖注入和服务定位大多数框架里都自带实现了。
就是練習實現一個思想,屬於进阶人员参考的东西。:laughing:
Voyage01
Voyage01
PHP 这些依赖注入和服务定位大多数框架里都自带实现了。
Jartin
Jartin
,
be-quiet
be-quiet
php 在 osc 深受偏见啊, 明明只是一种编程思想的 php 语言实现.
o0无忧亦无怖
o0无忧亦无怖

引用来自“big_cat”的评论

我仿着laravel的写了一套基于反射的ioc容器,交流交流,不过我没遵循psr的ioc容器标准
https://my.oschina.net/sallency/blog/1335254
q285753421
big_cat
big_cat
我仿着laravel的写了一套基于反射的ioc容器,交流交流,不过我没遵循psr的ioc容器标准
https://my.oschina.net/sallency/blog/1335254
big_cat
big_cat

引用来自“百世经纶之傲笑红尘”的评论

Javaer表示:前天,有群家伙抄Java框架Struts,造了个ThinkPHP后,今天有家伙抄SpringDI,写了这篇博客
写 java 的都这么可爱么,MVC和IOC容器居然成了java的专利了,佩服佩服
陈昊Sevens
陈昊Sevens
php是可以直接用字符串类名动态生成对象的。。。不需要像java这样搞
o0无忧亦无怖
o0无忧亦无怖

引用来自“宇润”的评论

设计模式听起来很复杂,其实很多用法实际写的时候可能已经用过了

引用来自“o0无忧亦无怖”的评论

必然,设计模式最初是为了弥补面向对象的不足而出现的

引用来自“johnlee007”的评论

这个说法有些不准确,还是认同下面这种解释:
设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式根本原因是为了代码复用,增加可维护性。非面向对象的编程语言也可以有设计模式
:laughing:我是说最初~~~后来大家都发现的确这样比较好,就形成了一堆的理论思想,统称设计模式。也就是“设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式根本原因是为了代码复用,增加可维护性”
johnlee007
johnlee007

引用来自“宇润”的评论

设计模式听起来很复杂,其实很多用法实际写的时候可能已经用过了

引用来自“o0无忧亦无怖”的评论

必然,设计模式最初是为了弥补面向对象的不足而出现的
这个说法有些不准确,还是认同下面这种解释:
设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式根本原因是为了代码复用,增加可维护性。非面向对象的编程语言也可以有设计模式
什么是依赖注入?

这篇文章是关于一般依赖关系注入和在PHP中实现依赖注入容器系列的第一部分。 今天我不会谈论容器然而我想以一些具体的示例介绍依赖注入的概念希望说明尝试去解决问题和它给开发者带来的好处。...

v8v9v3000
2014/05/07
0
0
依赖注入的简单记录

想理解php依赖注入和控制反转两个概念,就必须搞清楚如下的问题: DI——Dependency Injection 依赖注入 IoC——Inversion of Control 控制反转 1、参与者都有谁?  IOC/DI容器就是一个全局...

金于虎
2016/12/23
4
1
PHP反射机制实现自动依赖注入

转自 http://blog.csdn.net/qq_20678155/article/details/70158374 依赖注入又叫控制反转,使用过框架的人应该都不陌生。很多人一看名字就觉得是非常高大上的东西,就对它望而却步,今天抽空...

E狼
2017/12/10
0
0
JesusSlim/partini

partini a php web application framework used pinject. 一个基于pinject实现的phpweb框架 usage 使用composer安装 composer require jesussim/partini 参考 example . 基于pinject,php依赖......

JesusSlim
2016/07/31
0
0
laravel依赖注入和控制反转

依赖注入与控制反转 依赖注入 当我第一次接触这个词的时候,我是有些丈二和尚摸不着头脑的,至今我也是感到比较困惑的,所以今天我们来探索一下Laravel中的, 来好好的理解它。 控制反转 第一...

eatnothing
2016/05/15
525
0
PHP中的依赖注入(DI)容器

介绍 我们已经介绍过了PHP 反射API,阐明了什么是反射API,以及它的不同用途,其中一种 - 最常见的是将其与 一起使用,以下是本文的主要内容: 什么是依赖注入 注入对象的不同方式(以及为什...

如来神掌
06/03
0
0
PHPer、Laravel常见面试题及答案

最近社区里面有一篇文章引起了最多程序猿的关注,分享一下 Laravel、PHPer 面试可能会遇到的问题,看评论区不少小伙伴们被难倒,对于一些问题同样难倒了我(其实有很多啦),趁着周末有空,又...

angkee
2017/09/24
0
0
php中的数种依赖注入

经常看到却一直不甚理解的概念,依赖注入(DI)以及控制器反转(Ioc),找了几篇好的文章,分享一下。 自己理解的,依赖注入就是组件通过构造器,方法或者属性字段来获取相应的依赖对象。 举个现...

杨太化
2015/10/13
47
0
laravel 5.1 源码阅读

laravel类似其他php框架,采用了单一入口,入口文件为 /public/index.php /public/index.php主要做了以下几件事: 引用 /bootstrap/autoload.php, 这里是类的一些引入机制和优化,这里首先定义...

FalconChen
2016/03/25
173
1
菜菜鸟Zend Framework 2 不完全学习涂鸦(十三)-- 学习依赖注入

学习依赖注入 一、非常简短的介绍 Di(Dependency Injection) 依赖注入(Dependency Injection)是一个已经在 web 界讨论很多次的概念。这里的快速入门以此为目的,我们通过以下简单的代码解...

Jacky.Wang
2013/08/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

流利阅读笔记29-20180718待学习

高等教育未来成谜,前景到底在哪里? Ray 2018-07-18 1.今日导读 在这个信息爆炸的年代,获取知识是一件越来越容易的事情。人们曾经认为,如此的时代进步会给高等教育带来众多便利。但事实的...

aibinxiao
13分钟前
6
0
第15章FTP服务搭建与配置

15.1FTP介绍 FTP多用于Windows传文件到linux rz sz在文件超过4G,就无法使用了——>安装包yum install -y install lrzsz rz把 window 上的文件传输到 linux 上 sz 把 linux 上的文件传输到 ...

Linux学习笔记
20分钟前
0
0
OSChina 周三乱弹 —— 你被我从 osc 老婆们名单中踢出了

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @小鱼丁:分享五月天的单曲《后来的我们 (电影《后来的我们》片名曲)》: 《后来的我们 (电影《后来的我们》片名曲)》- 五月天 手机党少年们想...

小小编辑
25分钟前
6
1
Spring Boot Admin 2.0开箱体验

概述 在我之前的 《Spring Boot应用监控实战》 一文中,讲述了如何利用 Spring Boot Admin 1.5.X 版本来可视化地监控 Spring Boot 应用。说时迟,那时快,现在 Spring Boot Admin 都更新到 ...

CodeSheep
44分钟前
0
0
Python + Selenium + Chrome 使用代理 auth 的用户名密码授权

米扑代理,全球领导的代理品牌,专注代理行业近十年,提供开放、私密、独享代理,并可免费试用 米扑代理官网:https://proxy.mimvp.com 本文示例,是结合米扑代理的私密、独享、开放代理,专...

sunboy2050
今天
0
0
实现异步有哪些方法

有哪些方法可以实现异步呢? 方式一:java 线程池 示例: @Test public final void test_ThreadPool() throws InterruptedException { ScheduledThreadPoolExecutor scheduledThre......

黄威
今天
1
0
linux服务器修改mtu值优化cpu

一、jumbo frames 相关 1、什么是jumbo frames Jumbo frames 是指比标准Ethernet Frames长的frame,即比1518/1522 bit大的frames,Jumbo frame的大小是每个设备厂商规定的,不属于IEEE标准;...

六库科技
今天
0
0
牛客网刷题

1. 二维数组中的查找(难度:易) 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入...

大不了敲一辈子代码
今天
0
0
linux系统的任务计划、服务管理

linux任务计划cron 在linux下,有时候要在我们不在的时候执行一项命令,或启动一个脚本,可以使用任务计划cron功能。 任务计划要用crontab命令完成 选项: -u 指定某个用户,不加-u表示当前用...

黄昏残影
昨天
0
0
设计模式:单例模式

单例模式的定义是确保某个类在任何情况下都只有一个实例,并且需要提供一个全局的访问点供调用者访问该实例的一种模式。 实现以上模式基于以下必须遵守的两点: 1.构造方法私有化 2.提供一个...

人觉非常君
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部