PHP中的依赖注入(DI)容器

Wesley13
• 阅读 633

PHP中的依赖注入(DI)容器

介绍

我们已经介绍过了PHP 反射API,阐明了什么是反射API,以及它的不同用途,其中一种 - 最常见的是将其与 DI Container 一起使用,以下是本文的主要内容:

  • 什么是依赖注入
  • 注入对象的不同方式(以及为什么?)
  • 什么是 DI Container
  • ReflectionClassDI Container
  • 结论
  • 引用

什么是依赖注入

依赖注入是一种技术,一个对象提供另一个对象的依赖关系 - 依赖注入 - 维基百科

这是一个非常简单的概念,你可以将对象注入另一个对象,请查看以下示例:

class Profile
{
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

因为我们需要在类方法内中使用 $setting 对象,所以我们将它作为参数注入/传递。

注入对象的几种方式

有很多方法可以注入对象,这里有几种常用的方法:

  • Constructor Injection:它是如何通过类构造函数注入对象的,看下面例子:

    class Profile { private $setting; public function __construct(Setting $setting) { $this->setting = $setting; } }

    // 要想实例化Profile, 必须先实例化 Setting $setting = new Setting; $profile = new Profile($setting);

这是最常见的情况,创建松散耦合的组件非常有用。在为应用程序编写测试时也很有用。

  • Setter Injection:通过 setter 函数将对象注入到类中

    class Profile { private $setting; public function setSetting(Setting $setting) { $this->setting = $setting; } }

    // to instantiate Profile, you have the option to inject Setting object $setting = new Setting; $profile = new Profile(); $profile->setSetting($setting);

通过这种方式,可以随时在 Profile 类中使用它来注入。

什么是依赖入住容器

Dependency Injection Container 是在应用程序中管理注入和读取对象和第三方库的方式。

PHP-FIG PSR-11 告诉你如何在应用程序中使用一个容器:

<?php
interface ContainerInterface {
    public function get($id);
    public function has($id);
} 

对于具有两个主要函数 get()has(ContainerInterface 来说,这是一个非常简单的实现。

  • get():从容器中获取对象。
  • has():检查容器中是否有对象。

注意: PHP-FIG 的目标是为不同的实现制定标准,并将它们引入到PHP社区,点这里 查看更多内容,稍后我们将在另一个主题中讨论它。

ReflectionClass & DI Container

绝大多数框架都使用 DI Container,而不仅仅是在前面的章节中看到的内容,而且还解决了依赖关系 Autowiring

但是,resolving 依赖关系意味着什么,它意味着应用程序能够自动处理特定类的依赖关系,而不是手动执行。

为了理解的更加清楚点,我们来看一个例子:

class Profile
{
   protected $setting;

   public function __construct(Setting $setting)
   {
      $this->setting = $setting;
   }
}

这是我们实例化Profile类的方式:

$setting = new Setting;
$profile = new Profile($setting);

因此,为了实例化 Profile,你必须先实例化 Setting,然后在实例化之前手动解析 Profile 类的依赖关系。

我们可以以一种简洁的方式完成此操作,下面是一个非常简单的例子,它来自于 gist.github.com,使用一个 Container 类,该类自动为应用程序解析依赖关系:

<?php

/**
 * Class Container
 */
class Container
{
    /**
     * @var array
     */
    protected $instances = [];

    /**
     * @param      $abstract
     * @param null $concrete
     */
    public function set($abstract, $concrete = NULL)
    {
        if ($concrete === NULL) {
            $concrete = $abstract;
        }
        $this->instances[$abstract] = $concrete;
    }

    /**
     * @param       $abstract
     * @param array $parameters
     *
     * @return mixed|null|object
     * @throws Exception
     */
    public function get($abstract, $parameters = [])
    {
        // if we don't have it, just register it
        if (!isset($this->instances[$abstract])) {
            $this->set($abstract);
        }

        return $this->resolve($this->instances[$abstract], $parameters);
    }

    /**
     * resolve single
     *
     * @param $concrete
     * @param $parameters
     *
     * @return mixed|object
     * @throws Exception
     */
    public function resolve($concrete, $parameters)
    {
        if ($concrete instanceof Closure) {
            return $concrete($this, $parameters);
        }

        $reflector = new ReflectionClass($concrete);
        // check if class is instantiable
        if (!$reflector->isInstantiable()) {
            throw new Exception("Class {$concrete} is not instantiable");
        }

        // get class constructor
        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            // get new instance from class
            return $reflector->newInstance();
        }

        // get constructor params
        $parameters   = $constructor->getParameters();
        $dependencies = $this->getDependencies($parameters);

        // get new instance with dependencies resolved
        return $reflector->newInstanceArgs($dependencies);
    }

    /**
     * get all dependencies resolved
     *
     * @param $parameters
     *
     * @return array
     * @throws Exception
     */
    public function getDependencies($parameters)
    {
        $dependencies = [];
        foreach ($parameters as $parameter) {
            // get the type hinted class
            $dependency = $parameter->getClass();
            if ($dependency === NULL) {
                // check if default value for a parameter is available
                if ($parameter->isDefaultValueAvailable()) {
                    // get default value of parameter
                    $dependencies[] = $parameter->getDefaultValue();
                } else {
                    throw new Exception("Can not resolve class dependency {$parameter->name}");
                }
            } else {
                // get dependency resolved
                $dependencies[] = $this->get($dependency->name);
            }
        }

        return $dependencies;
    }
}

Container 类通过 set() 方法注册不同的类:

$container = new Container();
// let our application know that we are going to use the following
// classes, and this is optional, because if you didn't do it, it
// will be registered via the get() function
$container->set('Profile');

无论何时你想实例化 Profile 类,你都可以通过下面的方式轻松完成:

$profile = $container->get('Profile');

get() 函数通过 resolve() 函数,来检查 Profile 类的 __construct() 是否具有任何依赖关系,因此它将递归地解析它们(意味着如果 Setting 类具有依赖关系,它们也将被解析),否则,它将直接实例化 Profile 类。

ReflectionClassReflectionParameter 主要用于我们的 Container 类:

$reflector = new ReflectionClass($concrete);
// check if class is instantiable
$reflector->isInstantiable();

// get class constructor
$reflector->getConstructor();

// get new instance from class
$reflector->newInstance();

// get new instance with dependencies resolved
$reflector->newInstanceArgs($dependencies);

// get constructor params
$constructor->getParameters();

// get the type hinted class
$params->getClass()

// check if default value for a parameter is available
$parameter->isDefaultValueAvailable();

// get default value of parameter
$parameter->getDefaultValue();

总结

我们主要讨论了 Reflection API 的主要用法之一以及 DI Container 的主要功能,这是Autowiring,以及我们如何有效地使用 Reflection API,并通过简单的实现来阐明它。

引用

更多PHP 知识,可以前往 PHPCasts

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
PHP+jQuery寥寥几行代码轻松实现百度搜索那样的无刷新PJAX的分页列表和导航链接
!(https://static.oschina.net/uploads/space/2016/1208/171419_U00R_561214.png)PHP寥寥几行代码轻松实现百度搜索那样的分页列表和导航链接,某些语言的拥趸哭晕在厕所.<?php$apparray('db_prefix''
Wesley13 Wesley13
3年前
PHP 与 GO
PHP!输入图片说明(https://static.oschina.net/uploads/img/201608/22112754_GEtW.png"在这里输入图片标题")输出json!输入图片说明(https://static.oschina.net/uploads/img/201608/22112825_n
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这