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