侯体宗的博客
  • 首页
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

对PHP依赖注入的理解实例分析

php  /  管理员 发布于 7年前   193

本文实例讲述了对PHP依赖注入的理解。分享给大家供大家参考,具体如下:

看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!

首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。

在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。

class SomeComponent {  /**   * The instantiation of the connection is hardcoded inside   * the component so is difficult to replace it externally   * or change its behavior   */  public function someDbTask()  {    $connection = new Connection(array(      "host" => "localhost",      "username" => "root",      "password" => "secret",      "dbname" => "invo"    ));    // ...  }}$some = new SomeComponent();$some->someDbTask();

为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:

class SomeComponent {  protected $_connection;  /**   * Sets the connection externally   */  public function setConnection($connection)  {    $this->_connection = $connection;  }  public function someDbTask()  {    $connection = $this->_connection;    // ...  }}$some = new SomeComponent();//Create the connection$connection = new Connection(array(  "host" => "localhost",  "username" => "root",  "password" => "secret",  "dbname" => "invo"));//Inject the connection in the component$some->setConnection($connection);$some->someDbTask();

现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。

class Registry{  /**   * Returns the connection   */  public static function getConnection()  {    return new Connection(array(      "host" => "localhost",      "username" => "root",      "password" => "secret",      "dbname" => "invo"    ));  }}class SomeComponent{  protected $_connection;  /**   * Sets the connection externally   */  public function setConnection($connection){    $this->_connection = $connection;  }  public function someDbTask()  {    $connection = $this->_connection;    // ...  }}$some = new SomeComponent();//Pass the connection defined in the registry$some->setConnection(Registry::getConnection());$some->someDbTask();

现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:

class Registry{  protected static $_connection;  /**   * Creates a connection   */  protected static function _createConnection()  {    return new Connection(array(      "host" => "localhost",      "username" => "root",      "password" => "secret",      "dbname" => "invo"    ));  }  /**   * Creates a connection only once and returns it   */  public static function getSharedConnection()  {    if (self::$_connection===null){      $connection = self::_createConnection();      self::$_connection = $connection;    }    return self::$_connection;  }  /**   * Always returns a new connection   */  public static function getNewConnection()  {    return self::_createConnection();  }}class SomeComponent{  protected $_connection;  /**   * Sets the connection externally   */  public function setConnection($connection){    $this->_connection = $connection;  }  /**   * This method always needs the shared connection   */  public function someDbTask()  {    $connection = $this->_connection;    // ...  }  /**   * This method always needs a new connection   */  public function someOtherDbTask($connection)  {  }}$some = new SomeComponent();//This injects the shared connection$some->setConnection(Registry::getSharedConnection());$some->someDbTask();//Here, we always pass a new connection as parameter$some->someOtherDbTask(Registry::getConnection());

到此为止,我们已经看到了如何使用依赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。

例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:

//Create the dependencies or retrieve them from the registry$connection = new Connection();$session = new Session();$fileSystem = new FileSystem();$filter = new Filter();$selector = new Selector();//Pass them as constructor parameters$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);// ... or using setters$some->setConnection($connection);$some->setSession($session);$some->setFileSystem($fileSystem);$some->setFilter($filter);$some->setSelector($selector);

我想,我们不得不在应用程序的许多地方创建这个对象。如果你不需要依赖的组件后,我们又要去代码注入部分移除构造函数中的参数或者是setter方法。为了解决这个问题,我们再次返回去使用一个全局注册表来创建组件。但是,在创建对象之前,它增加了一个新的抽象层:

class SomeComponent{  // ...  /**   * Define a factory method to create SomeComponent instances injecting its dependencies   */  public static function factory()  {    $connection = new Connection();    $session = new Session();    $fileSystem = new FileSystem();    $filter = new Filter();    $selector = new Selector();    return new self($connection, $session, $fileSystem, $filter, $selector);  }}

这一刻,我们好像回到了问题的开始,我们正在创建组件内部的依赖,我们每次都在修改以及找寻一种解决问题的办法,但这都不是很好的做法。

一种实用和优雅的来解决这些问题,是使用容器的依赖注入,像我们在前面看到的,容器作为全局注册表,使用容器的依赖注入做为一种桥梁来解决依赖可以使我们的代码耦合度更低,很好的降低了组件的复杂性:

class SomeComponent{  protected $_di;  public function __construct($di)  {    $this->_di = $di;  }  public function someDbTask()  {    // Get the connection service    // Always returns a new connection    $connection = $this->_di->get('db');  }  public function someOtherDbTask()  {    // Get a shared connection service,    // this will return the same connection everytime    $connection = $this->_di->getShared('db');    //This method also requires a input filtering service    $filter = $this->_db->get('filter');  }}$di = new Phalcon\DI();//Register a "db" service in the container$di->set('db', function(){  return new Connection(array(    "host" => "localhost",    "username" => "root",    "password" => "secret",    "dbname" => "invo"  ));});//Register a "filter" service in the container$di->set('filter', function(){  return new Filter();});//Register a "session" service in the container$di->set('session', function(){  return new Session();});//Pass the service container as unique parameter$some = new SomeComponent($di);$some->someTask();

现在,该组件只有访问某种service的时候才需要它,如果它不需要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其他方面都不会影响到组件本身。我们的实现办法

Phalcon\DI 是一个实现了服务的依赖注入功能的组件,它本身也是一个容器。

由于Phalcon高度解耦,Phalcon\DI 是框架用来集成其他组件的必不可少的部分,开发人员也可以使用这个组件依赖注入和管理应用程序中不同类文件的实例。

基本上,这个组件实现了 Inversion of Control 模式。基于此,对象不再以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大降低了整体程序的复杂性,因为只有一个方法用以获得所需要的一个组件的依赖关系。
此外,这种模式增强了代码的可测试性,从而使它不容易出错。
在容器中注册服务

框架本身或开发人员都可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),可以从容器中请求调用组件B,而不是创建组件B的一个实例。

这种工作方式为我们提供了许多优点:

我们可以更换一个组件,从他们本身或者第三方轻松创建。
在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。

我们可以使用统一的方式从组件得到一个结构化的全局实例
服务可以通过以下几种方式注入到容器:

//Create the Dependency Injector Container$di = new Phalcon\DI();//By its class name$di->set("request", 'Phalcon\Http\Request');//Using an anonymous function, the instance will lazy loaded$di->set("request", function(){  return new Phalcon\Http\Request();});//Registering directly an instance$di->set("request", new Phalcon\Http\Request());//Using an array definition$di->set("request", array(  "className" => 'Phalcon\Http\Request'));

在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。

容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。
在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。

Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

//Register a service "db" with a class name and its parameters$di->set("db", array(  "className" => "Phalcon\Db\Adapter\Pdo\Mysql",  "parameters" => array(     "parameter" => array(        "host" => "localhost",        "username" => "root",        "password" => "secret",        "dbname" => "blog"     )  )));//Using an anonymous function$di->set("db", function(){  return new Phalcon\Db\Adapter\Pdo\Mysql(array(     "host" => "localhost",     "username" => "root",     "password" => "secret",     "dbname" => "blog"  ));});

以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:

$di->setParameter("db", 0, array(  "host" => "localhost",  "username" => "root",  "password" => "secret"));

从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:

$request = $di->get("request");

或者通过下面这种魔术方法的形式调用:

$request = $di->getRequest();

Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:

$request = $di->getShared("request");

参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:

$component = $di->get("MyComponent", array("some-parameter", "other"));

更多关于PHP相关内容感兴趣的读者可查看本站专题:《php面向对象程序设计入门教程》、《PHP基本语法入门教程》、《PHP运算与运算符用法总结》、《PHP网络编程技巧总结》、《PHP数组(Array)操作技巧大全》、《php字符串(string)用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

希望本文所述对大家PHP程序设计有所帮助。

您可能感兴趣的文章:

  • 理解php依赖注入和控制反转
  • PHP依赖注入原理与用法分析
  • PHP依赖注入(DI)和控制反转(IoC)详解
  • PHP基于反射机制实现自动依赖注入的方法详解
  • 浅析PHP类的反射来实现依赖注入过程
  • php中Ioc(控制反转)和Di(依赖注入)
  • PHP控制反转(IOC)和依赖注入(DI)
  • PHP进阶学习之依赖注入与Ioc容器详解
  • thinkphp5.1框架容器与依赖注入实例分析
  • PHP经典设计模式之依赖注入定义与用法详解
  • php反射学习之依赖注入示例
  • php中的依赖注入实例详解


  • 上一条:
    PHP对象链式操作实现原理分析
    下一条:
    mac下多个php版本快速切换的方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 用Time Warden监控PHP中的代码处理时间(0个评论)
    • 在PHP中使用array_pop + yield实现读取超大型目录功能示例(0个评论)
    • Property Hooks RFC在PHP 8.4中越来越接近现实(0个评论)
    • 近期文章
    • 在windows10中升级go版本至1.24后LiteIDE的Ctrl+左击无法跳转问题解决方案(0个评论)
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(95个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-06
    • 2017-07
    • 2017-08
    • 2017-09
    • 2017-11
    • 2017-12
    • 2018-01
    • 2018-02
    • 2018-03
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-09
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-09
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客