如何使用 php 写一个类似于 laravel 框架的服务容器()
如何使用 php 写一个类似于 laravel 框架的服务容器?
这篇文章可能文字不会太多,毕竟说再多都还不如直接看代码来的实在 ,以下我会把核心的代码都先贴出来,里面都有比较完善的注释信息,可以对着看。另外如果自己测试的话,可以直接下载我的源码,关于如何测试,源码中都有示例代码。
- Gitee 地址
- GitHub 地址
*/class Container
{/**
* 当前全局可用的容器(如果有)
*
* @var static
*/
private static $instance;
/**
* 容器的绑定
*
* @var array[]
*/
private $bindings = [];
/**
* 容器的共享实例
*
* @var object[]
*/
private $instances = [];
public function __construct()
{
$this->instances[Container::class] = $this;
}public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self;
}self::$instance->instances[Container::class] = self::$instance;
return self::$instance;
}/**
* 在容器中注册共享绑定
*
* @param $abstract
* @param $concrete
*/
public function singleton($abstract, $concrete)
{
$this->bind($abstract, $concrete, true);
}/**
* 向容器注册绑定
*
* @param $abstract
* @param $concrete
* @param false $shared
*/
public function bind($abstract, $concrete, $shared = false)
{
if ($concrete instanceof Closure) {
$this->bindings[$abstract] = compact('concrete', 'shared');
} else {
if (! is_string($concrete) || ! class_exists($concrete)) {
throw new InvalidArgumentException('Argument 2 must be callback or class.');
}
}$this->bindings[$abstract] = compact('concrete', 'shared');
}/**
* 将现有实例注册为容器中的共享实例
*
* @param string $abstract
* @param mixed $instance
* @return mixed
*/
public function instance($abstract, $instance)
{
$this->instances[$abstract] = $instance;
return $instance;
}/**
* 从容器解析给定类型
*
* @param string $abstract目标类的名称
* @param array $parameters实例化目标类时所需要的参数(非对象类型约束参数数组)
* @return mixed|object
*/
public function make(string $abstract, array $parameters = [])
{if (! isset($this->instances[$abstract]) && ! isset($this->bindings[$abstract])) {
if (! class_exists($abstract)) throw new InvalidArgumentException("Target class [$abstract] does not exist.");
}if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}try {if (isset($this->bindings[$abstract])) {
$concrete = $this->bindings[$abstract]['concrete'];
if (is_callable($concrete)) {
$instance = $this->resolveCallable($concrete, $parameters);
} else {
$instance = $this->resolveClass($concrete, $parameters);
}
} else {
$instance = $this->resolveClass($abstract, $parameters);
}if (isset($this->bindings[$abstract]) && $this->bindings[$abstract]['shared']) {
$this->instances[$abstract] = $instance;
}return $instance;
} catch (\Exception $exception) {
echo($exception->getMessage() . PHP_EOL);
print_r($exception->getTraceAsString());
}}/**
* 解决回调函数时的依赖
*
* @param callable $callbackName目标回调函数
* @param array $realArgs
* @return mixed
* @throws ReflectionException
*/
private function resolveCallable(callable $callbackName, array $realArgs = [])
{
$reflector = new ReflectionFunction($callbackName);
// 获取回调函数的参数列表
$parameters = $reflector->getParameters();
$list = [];
if (count($parameters) > 0) {
$list = $this->resolveDependencies($parameters, $realArgs);
}// 调用函数参数
return $reflector->invokeArgs($list);
}/**
* 解决对象时的依赖
*
* @param string|object $className目标类的名称
* @param array $realArgs
* @return object目标类对应的实例对象
* @throws ReflectionException
*/
private function resolveClass($className, array $realArgs = [])
{
try {
// 对目标类进行反射(解析其方法、属性)
$reflector = new ReflectionClass($className);
} catch (ReflectionException $e) {
throw new RuntimeException("Target class [$className] does not exist.", 0, $e);
}if (! $reflector->isInstantiable()) {// 检查类是否可以实例化
throw new RuntimeException("Target class [$className] is not instantiable.");
}// 获取目标类的构造函数,当类不存在构造函数时返回 null
$constructor = $reflector->getConstructor();
// 没有构造函数,则直接实例化
if (is_null($constructor)) {
// return new $className;
// 或者也可以直接这样去实例化,因为目标类没有构造函数,不需要传参数
return $reflector->newInstance();
}// 获取构造函数的参数列表
$parameters = $constructor->getParameters();
// 递归解析构造函数的参数
$list = $this->resolveDependencies($parameters, $realArgs);
// 从给出的参数创建一个新的类实例
return $reflector->newInstanceArgs($list);
}/**
* 递归解析依赖树
*
* @param array $dependencies目标类的构造函数参数列表
* @param array $parameters实例化目标类时的其他参数(非类型提示参数)
* @return array实例化目标类时构造函数所需的所有参数
*/
private function resolveDependencies(array $dependencies, array $parameters = [])
{
// 用于存储所有的参数
$results = [];
foreach ($dependencies as $dependency) {// 获取类型提示类
$obj = $dependency->getClass();
// 如果类为 null,则表示依赖项是字符串或其他类型
if (is_null($obj)) {$parameterName = $dependency->getName();
// 获取参数的名称// 检查参数是否有默认值
if (! $dependency->isDefaultValueAvailable()) {
if (! isset($parameters[$parameterName])) {
throw new RuntimeException($parameterName . ' has no value');
} else {
$results[] = $parameters[$parameterName];
}
} else {// 参数有默认值的时候
if (isset($parameters[$parameterName])) {
$results[] = $parameters[$parameterName];
} else {
$results[] = $dependency->getDefaultValue();
// 获取参数的默认值
}
}} else {// 类型提示确定是一个类时,则需要递归处理依赖项
$objName = $obj->getName();
// 获取依赖项的类名
if (! class_exists($objName)) {
throw new RuntimeException('Unable to load class: ' . $objName);
} else {
$results[] = $this->make($objName);
}
}}return $results;
}}
推荐阅读
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- 由浅入深理解AOP
- 如何寻找情感问答App的分析切入点
- 【译】20个更有效地使用谷歌搜索的技巧
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- 如何在Mac中的文件选择框中打开系统隐藏文件夹
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)
- java中如何实现重建二叉树