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

輸入圖片說明

介紹

咱們已經介紹過了PHP 反射API,闡明瞭什麼是反射API,以及它的不一樣用途,其中一種 - 最多見的是將其與 DI Container 一塊兒使用,如下是本文的主要內容:php

  • 什麼是依賴注入
  • 注入對象的不一樣方式(以及爲何?)
  • 什麼是 DI Container
  • ReflectionClassDI Container
  • 結論
  • 引用

什麼是依賴注入

依賴注入是一種技術,一個對象提供另外一個對象的依賴關係 - 依賴注入 - 維基百科html

這是一個很是簡單的概念,你能夠將對象注入另外一個對象,請查看如下示例:git

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

由於咱們須要在類方法內中使用 $setting 對象,因此咱們將它做爲參數注入/傳遞。github

注入對象的幾種方式

有不少方法能夠注入對象,這裏有幾種經常使用的方法:app

  • 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 告訴你如何在應用程序中使用一個容器:this

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

對於具備兩個主要函數 get()has(ContainerInterface 來講,這是一個很是簡單的實現。.net

  • 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

相關文章
相關標籤/搜索