php依赖自动注入的实现的,实现PHP的自动依赖注入容器 EasyDI容器

这篇文章主要介绍了关于实现PHP的自动依赖注入容器 EasyDI容器,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

1. 前言

2. 项目代码结构

3. 容器完整代码3.4.1 解决类构造函数依赖

3.4.2 解决 callable 的参数依赖

3.1 容器主要提供方法

3.2 符合PSR-11标准

3.3 容器的基本存储

3.4 自动依赖解决

4. 未完..不一定续

1. 前言

在看了一些容器实现代码后, 就手痒想要自己实现一个, 因此也就有了本文接下来的内容.

首先, 实现的容器需要具有以下几点特性:符合PSR-11标准

实现基本的容器存储功能

具有自动依赖解决能力

本项目代码由GitHub托管

可使用Composer进行安装 composer require yjx/easy-di

2. 项目代码结构|-src

|-Exception

|-InstantiateException.php (实现Psr\Container\ContainerExceptionInterface)

|-InvalidArgumentException.php (实现Psr\Container\ContainerExceptionInterface)

|-UnknownIdentifierException.php (实现Psr\Container\NotFoundExceptionInterface)

|-Container.php # 容器|-tests

|-UnitTest

|-ContainerTest.php

3. 容器完整代码代码版本 v1.0.1<?php

namespace EasyDI;

use EasyDI\Exception\UnknownIdentifierException;

use EasyDI\Exception\InvalidArgumentException;

use EasyDI\Exception\InstantiateException;

use Psr\Container\ContainerExceptionInterface;

use Psr\Container\ContainerInterface;use Psr\Container\NotFoundExceptionInterface;

class Container implements ContainerInterface{

/**

* 保存 参数, 已实例化的对象

* @var array

*/

private $instance = []; private $shared = []; private $raw = []; private $params = []; /**

* 保存 定义的 工厂等

* @var array

*/

private $binding = []; public function __construct()

{

$this->raw(ContainerInterface::class, $this);

$this->raw(self::class, $this);

}

/**

* Finds an entry of the container by its identifier and returns it.

*

* @param string $id Identifier of the entry to look for.

*

* @throws NotFoundExceptionInterface No entry was found for **this** identifier.

* @throws ContainerExceptionInterface Error while retrieving the entry.

*

* @return mixed Entry.

*/ public function get($id, $parameters = [], $shared=false)

{

if (!$this->has($id)) { throw new UnknownIdentifierException($id);

}

if (array_key_exists($id, $this->raw)) {

return $this->raw[$id];

}

if (array_key_exists($id, $this->instance)) {

return $this->instance[$id];

}

$define = array_key_exists($id, $this->binding) ? $this->binding[$id] : $id;

if ($define instanceof \Closure) {

$instance = $this->call($define, $parameters);

} else {

// string

$class = $define;

$params = (empty($this->params[$id]) ? [] : $this->params[$id]) + $parameters;

// Case: "\\xxx\\xxx"=>"abc"

if ($id !== $class && $this->has($class)) {

$instance = $this->get($class, $params);

} else {

$dependencies = $this->getClassDependencies($class, $params);

if (is_null($dependencies) || empty($dependencies)) {

$instance = $this->getReflectionClass($class)->newInstanceWithoutConstructor();

} else {

$instance = $this->getReflectionClass($class)->newInstanceArgs($dependencies);

}

}

} if ($shared || (isset($this->shared[$id]) && $this->shared[$id])) {

$this->instance[$id] = $instance;

} return $instance;

} /**

* @param callback $function

* @param array $parameters

* @return mixed

* @throws InvalidArgumentException 传入错误的参数

* @throws InstantiateException

*/

public function call($function, $parameters=[], $shared=false)

{

//参考 http://php.net/manual/zh/function.call-user-func-array.php#121292 实现解析$function

$class = null; $method = null; $object = null; // Case1: function() {}

if ($function instanceof \Closure) { $method = $function;

} elseif (is_array($function) && count($function)==2) { // Case2: [$object, $methodName]

if (is_object($function[0])) { $object = $function[0]; $class = get_class($object);

} elseif (is_string($function[0])) { // Case3: [$className, $staticMethodName]

$class = $function[0];

} if (is_string($function[1])) { $method = $function[1];

}

} elseif (is_string($function) && strpos($function, '::') !== false) { // Case4: "class::staticMethod"

list($class, $method) = explode('::', $function);

} elseif (is_scalar($function)) { // Case5: "functionName"

$method = $function;

} else { throw new InvalidArgumentException("Case not allowed! Invalid Data supplied!");

} try { if (!is_null($class) && !is_null($method)) { $reflectionFunc = $this->getReflectionMethod($class, $method);

} elseif (!is_null($method)) { $reflectionFunc = $this->getReflectionFunction($method);

} else { throw new InvalidArgumentException("class:$class method:$method");

}

} catch (\ReflectionException $e) {// var_dump($e->getTraceAsString());

throw new InvalidArgumentException("class:$class method:$method", 0, $e);

}

$parameters = $this->getFuncDependencies($reflectionFunc, $parameters);

if ($reflectionFunc instanceof \ReflectionFunction) {

return $reflectionFunc->invokeArgs($parameters);

} elseif ($reflectionFunc->isStatic()) {

return $reflectionFunc->invokeArgs(null, $parameters);

} elseif (!empty($object)) {

return $reflectionFunc->invokeArgs($object, $parameters);

} elseif (!is_null($class) && $this->has($class)) {

$object = $this->get($class, [], $shared);

return $reflectionFunc->invokeArgs($object, $parameters);

}

throw new InvalidArgumentException("class:$class method:$method, unable to invoke.");

} /**

* @param $class

* @param array $parameters

* @throws \ReflectionException

*/

protected function getClassDependencies($class, $parameters=[])

{

// 获取类的反射类

$reflectionClass = $this->getReflectionClass($class);

if (!$reflectionClass->isInstantiable()) {

throw new InstantiateException($class);

} // 获取构造函数反射类

$reflectionMethod = $reflectionClass->getConstructor();

if (is_null($reflectionMethod)) {

return null;

}

return $this->getFuncDependencies($reflectionMethod, $parameters, $class);

}

protected function getFuncDependencies(\ReflectionFunctionAbstract $reflectionFunc, $parameters=[], $class="")

{

$params = []; // 获取构造函数参数的反射类

$reflectionParameterArr = $reflectionFunc->getParameters();

foreach ($reflectionParameterArr as $reflectionParameter) {

$paramName = $reflectionParameter->getName();

$paramPos = $reflectionParameter->getPosition();

$paramClass = $reflectionParameter->getClass();

$context = ['pos'=>$paramPos, 'name'=>$paramName, 'class'=>$paramClass, 'from_class'=>$class];

// 优先考虑 $parameters

if (isset($parameters[$paramName]) || isset($parameters[$paramPos])) {

$tmpParam = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$paramPos];

if (gettype($tmpParam) == 'object' && !is_a($tmpParam, $paramClass->getName())) {

throw new InstantiateException($class."::".$reflectionFunc->getName(), $parameters + ['__context'=>$context, 'tmpParam'=>get_class($tmpParam)]);

}

$params[] = $tmpParam;//

$params[] = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$pos];

} elseif (empty($paramClass)) {

// 若参数不是class类型

// 优先使用默认值, 只能用于判断用户定义的函数/方法, 对系统定义的函数/方法无效, 也同样无法获取默认值

if ($reflectionParameter->isDefaultValueAvailable()) { $params[] = $reflectionParameter->getDefaultValue();

} elseif ($reflectionFunc->isUserDefined()) {

throw new InstantiateException("UserDefined. ".$class."::".$reflectionFunc->getName());

} elseif ($reflectionParameter->isOptional()) { break;

} else { throw new InstantiateException("SystemDefined. ".$class."::".$reflectionFunc->getName());

}

} else { // 参数是类类型, 优先考虑解析

if ($this->has($paramClass->getName())) { $params[] = $this->get($paramClass->getName());

} elseif ($reflectionParameter->allowsNull()) { $params[] = null;

} else { throw new InstantiateException($class."::".$reflectionFunc->getName()." {$paramClass->getName()} ");

}

}

} return $params;

} protected function getReflectionClass($class, $ignoreException=false)

{

static $cache = []; if (array_key_exists($class, $cache)) { return $cache[$class];

} try { $reflectionClass = new \ReflectionClass($class);

} catch (\Exception $e) { if (!$ignoreException) { throw new InstantiateException($class, 0, $e);

} $reflectionClass = null;

} return $cache[$class] = $reflectionClass;

} protected function getReflectionMethod($class, $name)

{

static $cache = []; if (is_object($class)) { $class = get_class($class);

} if (array_key_exists($class, $cache) && array_key_exists($name, $cache[$class])) { return $cache[$class][$name];

} $reflectionFunc = new \ReflectionMethod($class, $name); return $cache[$class][$name] = $reflectionFunc;

} protected function getReflectionFunction($name)

{

static $closureCache; static $cache = []; $isClosure = is_object($name) && $name instanceof \Closure; $isString = is_string($name); if (!$isString && !$isClosure) { throw new InvalidArgumentException("$name can't get reflection func.");

} if ($isString && array_key_exists($name, $cache)) { return $cache[$name];

} if ($isClosure) { if (is_null($closureCache)) { $closureCache = new \SplObjectStorage();

} if ($closureCache->contains($name)) { return $closureCache[$name];

}

} $reflectionFunc = new \ReflectionFunction($name); if ($isString) { $cache[$name] = $reflectionFunc;

} if ($isClosure) { $closureCache->attach($name, $reflectionFunc);

} return $reflectionFunc;

} /**

* Returns true if the container can return an entry for the given identifier.

* Returns false otherwise.

*

* `has($id)` returning true does not mean that `get($id)` will not throw an exception.

* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.

*

* @param string $id Identifier of the entry to look for.

*

* @return bool

*/

public function has($id)

{

$has = array_key_exists($id, $this->binding) || array_key_exists($id, $this->raw) || array_key_exists($id, $this->instance);

if (!$has) { $reflectionClass = $this->getReflectionClass($id, true);

if (!empty($reflectionClass)) { $has = true;

}

} return $has;

} public function needResolve($id)

{

return !(array_key_exists($id, $this->raw) && (array_key_exists($id, $this->instance) && $this->shared[$id]));

} public function keys()

{

return array_unique(array_merge(array_keys($this->raw), array_keys($this->binding), array_keys($this->instance)));

} public function instanceKeys()

{

return array_unique(array_keys($this->instance));

} public function unset($id)

{

unset($this->shared[$id], $this->binding[$id], $this->raw[$id], $this->instance[$id], $this->params[$id]);

} public function singleton($id, $value, $params=[])

{

$this->set($id, $value, $params, true);

} /**

* 想好定义数组, 和定义普通项

* @param $id

* @param $value

* @param bool $shared

*/

public function set($id, $value, $params=[], $shared=false)

{

if (is_object($value) && !($value instanceof \Closure)) { $this->raw($id, $value); return;

} elseif ($value instanceof \Closure) { // no content

} elseif (is_array($value)) { $value = [ 'class' => $id, 'params' => [], 'shared' => $shared

] + $value; if (!isset($value['class'])) { $value['class'] = $id;

} $params = $value['params'] + $params; $shared = $value['shared']; $value = $value['class'];

} elseif (is_string($value)) { // no content

} $this->binding[$id] = $value; $this->shared[$id] = $shared; $this->params[$id] = $params;

} public function raw($id, $value)

{

$this->unset($id); $this->raw[$id] = $value;

} public function batchRaw(array $data)

{

foreach ($data as $key=>$value) { $this->raw($key, $value);

}

} public function batchSet(array $data, $shared=false)

{

foreach ($data as $key=>$value) { $this->set($key, $value, $shared);

}

}

}

3.1 容器主要提供方法

容器提供方法:

- raw(string $id, mixed $value)

适用于保存参数, $value可以是任何类型, 容器不会对其进行解析.set(string $id, \Closure|array|string $value, array $params=[], bool $shared=false)

定义服务

singleton(string $id, \Closure|array|string $value, array $params=[])

等同调用set($id, $value, $params, true)

has(string $id)

判断容器是否包含$id对应条目

get(string $id, array $params = [])

从容器中获取

params可优先参与到条目实例化过程中的依赖注入

call(callable $function, array $params=[])

利用容器来调用callable, 由容器自动注入依赖.

unset(string $id)

从容器中移除$id对应条目

3.2 符合PSR-11标准

EasyDI(本容器)实现了 Psr\Container\ContainerInterface 接口, 提供 has($id) 和 get($id, $params=[]) 两个方法用于判断及获取条目.

对于无法解析的条目识别符, 则会抛出异常(实现了 NotFoundExceptionInterface 接口).

3.3 容器的基本存储

容器可用于保存 不被解析的条目, 及自动解析的条目.不被解析的条目

主要用于保存 配置参数, 已实例化对象, 不被解析的闭包 等

自动解析的条目

在 get(...) 时会被容器自动解析, 若是 闭包 则会自动调用, 若是 类名 则会实例化, 若是 别名 则会解析其对应的条目.

3.4 自动依赖解决

EasyDI 在调用 闭包 及 实例化 类 已经 调用函数/方法(call()) 时能够自动注入所需的依赖, 其中实现的原理是使用了PHP自带的反射API.

此处主要用到的反射API如下:ReflectionClass

ReflectionFunction

ReflectionMethod

ReflectionParameter

3.4.1 解决类构造函数依赖

解析的一般步骤:获取类的反射类 $reflectionClass = new ReflectionClass($className)

判断能够实例化 $reflectionClass->isInstantiable()

若能实例化, 则获取对应的构造函数的反射方法类 $reflectionMethod = $reflectionClass->getConstructor()

3.1. 若返回null, 则表示无构造函数可直接跳到步骤6

3.2 若返回ReflectionMethod实例, 则开始解析其参数

获取构造函数所需的所有依赖参数类 $reflectionParameters = $reflectionMethod->getParameters

逐个解析依赖参数 $reflectionParameter

5.1 获取参数对应名及位置 $reflectionParameter->getName(), $reflectionParameter->getClass()

5.2 获取参数对应类型 $paramClass = $reflectionParameter->getClass()

5.2.1 若本次解析手动注入了依赖参数, 则根据参数位置及参数名直接使用传入的依赖参数 Eg. $container->get($xx, [1=>123, 'e'=>new \Exception()])

5.2.2 若参数是标量类型, 若参数有默认值($reflectionParameter->isDefaultValueAvailable())则使用默认值, 否则抛出异常(无法处理该依赖)

5.2.3 若参数是 class 类型, 若容器可解析该类型, 则由容器自动实例化 $this->get($paramClass->getName()), 若无法解析但该参数允许null, 则传入null值, 否则抛出异常(无法处理来依赖)

若依赖参数为空则调用 $reflectionClass->newInstanceWithoutConstructor(), 否则调用 $reflectionClass->newInstanceArgs($dependencies); //$dependencies为步骤5中构造的依赖参数数组具体完整代码请参照容器类的 getClassDependencies(...) 方法.

3.4.2 解决 callable 的参数依赖

使用 call(...) 来调用 可调用 时, 自动解决依赖同样类似上述过程, 只是需要区分是 类函数, 类静态方法 还是 普通方法, 并相应的使用不同的反射类来解析,具体完整代码请参照容器类的 call(...) 方法class UserManager{

private $mailer; public function __construct(Mailer $mailer)

{

$this->mailer = $mailer;

} public function register($email, $password)

{

// The user just registered, we create his account

// ...

// We send him an email to say hello!

$this->mailer->mail($email, 'Hello and welcome!');

} public function quickSend(Mailer $mailer, $email, $password)

{

$mailer->mail($email, 'Hello and welcome!');

}

}function testFunc(UserManager $manager){

return "test";

}// 实例化容器$c = new EasyDI\Container();// 输出: 'test'echo $c->call('testFunc')."\n";

// 输出: 'test'echo $c->call(function (UserManager $tmp) {

return 'test';

});

// 自动实例化UserManager对象 [$className, $methodName]$c->call([UserManager::class, 'register'], ['password'=>123, 'email'=>'1@1.1']);

// 自动实例化UserManager对象 $methodFullName$c->call(UserManager::class.'::'.'register', ['password'=>123, 'email'=>'1@1.1']);

// 调用类的静态方法 [$className, $staticMethodName]

$c->call([UserManager::class, 'quickSend'], ['password'=>123, 'email'=>'1@1.1']);

// 使用字符串调用类的静态方法 $staticMethodFullName$c->call(UserManager::class.'::'.'quickSend', ['password'=>123, 'email'=>'1@1.1']);

// [$obj, $methodName]

$c->call([new UserManager(new Mailer()), 'register'], ['password'=>123, 'email'=>'1@1.1']);

// [$obj, $staticMethodName]

$c->call([new UserManager(new Mailer()), 'quickSend'], ['password'=>123, 'email'=>'1@1.1']);

4. 未完..不一定续

暂时写到此处.

后续项目最新代码直接在 GitHub 上维护, 该博文后续视评论需求来决定是否补充.

相关推荐:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值