介绍
我们已经介绍过了PHP 反射API,阐明了什么是反射API,以及它的不同用途,其中一种 - 最常见的是将其与 DI Container
一起使用,以下是本文的主要内容:
- 什么是依赖注入
- 注入对象的不同方式(以及为什么?)
- 什么是
DI Container
ReflectionClass
和DI 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
类。
ReflectionClass
和 ReflectionParameter
主要用于我们的 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
,并通过简单的实现来阐明它。
引用
- 介绍反射API
- http://php.net/manual/en/book.reflection.php
- https://en.wikipedia.org/wiki/Dependency_injection
- http://www.php-fig.org/psr/psr-11/
- https://symfony.com/doc/current/service_container/autowiring.html
- https://gist.github.com/MustafaMagdi/2bb27aebf6ab078b1f3e5635c0282fac
更多PHP 知识,可以前往 PHPCasts