如何应用 php 写一个相似于 laravel 框架的服务容器?
这篇文章可能文字不会太多,毕竟说再多都还不如间接看代码来的切实 😀,以下我会把外围的代码都先贴出来,外面都有比较完善的正文信息,能够对着看。另外如果本人测试的话,能够间接下载我的源码,对于如何测试,源码中都有示例代码。
- Gitee 地址
- GitHub 地址
以下是实现容器的外围代码
<?php
/**
* 实现一个简略的 php 容器
*
* Created by PhpStorm
* User: Alex
* Date: 2021-08-03 17:51
* E-mail: <276558492@qq.com>
*/
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;
}
}