├── README.md ├── composer.json └── src └── Di ├── Container.php ├── ContainerAwareInterface.php ├── ContainerAwareTrait.php ├── ContainerInterface.php ├── Service.php └── ServiceInterface.php /README.md: -------------------------------------------------------------------------------- 1 | Soli Dependency Injection Container 2 | ------------------ 3 | 4 | 当前项目参考了 Phalcon 框架的[依赖注入与服务定位器] 和 Laravel 框架的[服务容器] 实现。 5 | 6 | 服务容器的目的为了降低代码的耦合度,提高应用的可维护性;是用于管理类依赖和执行依赖注入的工具。 7 | 8 | [![Build Status](https://travis-ci.org/soliphp/di.svg?branch=master)](https://travis-ci.org/soliphp/di) 9 | [![Coverage Status](https://coveralls.io/repos/github/soliphp/di/badge.svg?branch=master)](https://coveralls.io/github/soliphp/di?branch=master) 10 | [![License](https://poser.pugx.org/soliphp/di/license)](https://packagist.org/packages/soliphp/di) 11 | 12 | 13 | ## Table of Contents 14 | 15 | * [安装](#安装) 16 | * [先看一个简单的例子](#先看一个简单的例子) 17 | * [注册服务](#注册服务) 18 | * [类名](#类名) 19 | * [类实例](#类实例) 20 | * [闭包/匿名函数](#闭包匿名函数) 21 | * [使用 $this 访问容器中的其他服务](#使用-this-访问容器中的其他服务) 22 | * [注册接口到实现](#注册接口到实现) 23 | * [共享(单例)服务](#共享单例服务) 24 | * [获取服务](#获取服务) 25 | * [get() 服务ID](#get-服务id) 26 | * [get() 类名](#get-类名) 27 | * [对象属性](#对象属性) 28 | * [数组下标](#数组下标) 29 | * [$this](#this) 30 | * [静态方式访问容器](#静态方式访问容器) 31 | * [容器感知](#容器感知) 32 | * [别名](#别名) 33 | * [为已注册服务定义别名](#为已注册服务定义别名) 34 | * [为类名定义别名](#为类名定义别名) 35 | * [示例](#示例) 36 | * [API 参考](#api-参考) 37 | * [测试](#测试) 38 | * [License](#license) 39 | 40 | ## 安装 41 | 42 | 使用 `composer` 进行安装: 43 | 44 | composer require soliphp/di 45 | 46 | ## 先看一个简单的例子 47 | 48 | use Psr\Log\LoggerInterface; 49 | 50 | class ExceptionHandler 51 | { 52 | /** 53 | * 日志记录器 54 | * 55 | * @var \Psr\Log\LoggerInterface 56 | */ 57 | protected $logger; 58 | 59 | public function __construct(LoggerInterface $logger) 60 | { 61 | $this->logger = $logger; 62 | } 63 | 64 | /** 65 | * 异常报告 66 | */ 67 | public function report(\Throwable $e) 68 | { 69 | $this->logger->error($e->getMessage()); 70 | } 71 | } 72 | 73 | 74 | 异常处理类 `ExceptionHandler` 需要 `logger` 记录(报告)异常, 75 | 因此,我们需要`注入`实现了 `Psr\Log\LoggerInterface` 接口的类。 76 | 而 `logger` 的实现是要发短信、发邮件或进日志系统,这个 `ExceptionHandler` 77 | 是不关心的,所以我们可以轻易地将 `logger` 切换为另一个实现。 78 | 在为应用编写测试时,也可以轻松的模拟 `Psr\Log\LoggerInterface` 的实现。 79 | 80 | ## 注册服务 81 | 82 | 服务的注册阶段,仅仅是存储服务定义的格式,并不会调用服务的定义; 83 | 而在获取服务(即使用服务)时,对服务定义进行调用,得到服务定义的执行结果。 84 | 这样便实现了对`服务的延迟加载`,避免实例化请求中未用到的服务。 85 | 86 | 以下 `$container` 变量均为 `Soli\Di\Container` 实例。 87 | 88 | ### 类名 89 | 90 | use Soli\Di\Container; 91 | 92 | $container = new Container(); 93 | 94 | $container->set('someService', \SomeNamespace\SomeService::class); 95 | 96 | ### 类实例 97 | 98 | $container->set('someService', new \SomeNamespace\SomeService()); 99 | 100 | ### 闭包/匿名函数 101 | 102 | // 注册服务,存储服务的定义 103 | $container->set('someService', function () { 104 | return new SomeService(); 105 | }); 106 | 107 | #### 使用 $this 访问容器中的其他服务 108 | 109 | 当使用匿名函数注册服务时,函数体内可以使用 `$this` 表示当前的 `$container` 容器类实例, 110 | 直接访问容器中的其他服务,便于服务之间进行交互。 111 | 112 | // 注册服务,存储服务的定义 113 | $container->set('someService', function () { 114 | // 此处的 $tihs 即是当前的 $container 容器类实例 115 | // 通过 $this 直接访问容器中的其他服务 116 | var_dump($this->otherService); 117 | }); 118 | 119 | ### 注册接口到实现 120 | 121 | `注册接口到实现类`: 122 | 123 | $container->set('Database\AdapterInterface', 'Database\Adapter\Mysql'); 124 | 125 | 如果一个类需要处理后才会得到一个适用的实例,那么这里的实现就是一个匿名函数, 126 | 可以`注册接口到匿名函数`,注意匿名函数的返回实例即可: 127 | 128 | use Monolog\Logger; 129 | use Monolog\Handler\StreamHandler; 130 | 131 | $container->set('Psr\Log\LoggerInterface', function () { 132 | // create a log channel 133 | $log = new Logger('name'); 134 | $log->pushHandler(new StreamHandler('/path/to/your.log', Logger::WARNING)); 135 | 136 | return $log; 137 | }); 138 | 139 | ### 共享(单例)服务 140 | 141 | 共享服务意味着让服务以单例模式运行,之后的每次请求都会从容器取到同一个实例。 142 | 否则每次都会执行服务解析,返回新的服务实例。 143 | 144 | `默认使用 set() 注册的服务都为共享服务`。 145 | 146 | 如果需要`每次都使用新的实例`,则需要将第三个参数 `$shared 设置为 false`。 147 | 148 | $container->set('newInstance', 'SomeNamespace\SomeService', false); 149 | 150 | ## 获取服务 151 | 152 | 获取服务时,调用服务定义,返回服务实例。 153 | 154 | 获取服务有多种方法: 155 | 156 | ### get() 服务ID 157 | 158 | 服务ID或者叫服务名称,使用 `get()` 方法获取已注册`服务ID`提供的服务: 159 | 160 | $service = $container->get('someService'); 161 | 162 | ### get() 类名 163 | 164 | 对于`类名`无论是否已注册为服务,都可以直接通过容器的 `get()` 方法获取到它的单例: 165 | 166 | $service = $container->get('SomeNamespace\UnregisteredClass'); 167 | 168 | 这对于我们日常开发中经常用到的单例模式,将格外的方便。 169 | 170 | 如果某些类的依赖项不能通过容器去解析,可以通过将它们作为关联数组传递到 `get()` 方法的第二个参数注入它们。 171 | 172 | $service = $container->get('SomeNamespace\UnregisteredClass', ['id' => 1]); 173 | 174 | ### 对象属性 175 | 176 | 使用访问`对象属性`的方式,获取服务: 177 | 178 | $service = $container->someService; 179 | 180 | ### 数组下标 181 | 182 | 使用访问`数组下标`的方式,获取服务: 183 | 184 | $service = $container['someService']; 185 | 186 | ### $this 187 | 188 | 匿名函数注册服务时[使用 $this 访问容器中的其他服务](#使用-this-访问容器中的其他服务)。 189 | 190 | ## 静态方式访问容器 191 | 192 | $container = Container::instance(); 193 | 194 | $container->get('someService'); 195 | 196 | 这里需要注意一下,在调用 `Container::instance()` 之前,一定已经执行过 `new Container()` 实例化操作。 197 | 198 | ## 容器感知 199 | 200 | 如果某个服务实现了 `Soli\Di\ContainerAwareInterface` 容器感知接口, 201 | 在获取服务时,会自动调用 `setContainer()` 为其设置容器实例。 202 | 203 | ## 别名 204 | 205 | 别名是用来给服务起不同的名字,以便可以使用不同的名字获取同一个服务。 206 | 207 | 注意不要定义与服务名称相同的别名。 208 | 209 | ### 为已注册服务定义别名 210 | 211 | 首先注册以 `container` 为名称的服务: 212 | 213 | $container->set('container', $container); 214 | 215 | 为 `container` 服务定义三个别名 `Soli\Di\Container`, `Soli\Di\ContainerInterface`, `Psr\Container\ContainerInterface`: 216 | 217 | $container->alias('Soli\Di\Container', 'container'); 218 | $container->alias('Soli\Di\ContainerInterface', 'container'); 219 | $container->alias('Psr\Container\ContainerInterface', 'container'); 220 | 221 | 如果容器中其他服务的构造函数使用了以上三个别名的「类型提示」,则会为其「自动注入」对应服务实例。 222 | 223 | use Psr\Container\ContainerInterface; 224 | 225 | class Component 226 | { 227 | protected $container; 228 | 229 | public function __construct(ContainerInterface $container) 230 | { 231 | $this->container = $container; 232 | } 233 | } 234 | 235 | // 将自动注入 $container 参数 236 | $component = $container->get(Component::class); 237 | 238 | ### 为类名定义别名 239 | 240 | 由于类名可直接通过容器获取其实例,所以类名是无需定义的服务名称。 241 | 242 | 那么我们便可以直接为类名定义别名: 243 | 244 | $container->alias('Soli\Di\ContainerInterface', 'Soli\Di\Container'); 245 | $container->alias('Psr\Container\ContainerInterface', 'Soli\Di\Container'); 246 | 247 | ## 示例 248 | 249 | 在 [examples] 文件夹下提供了一个类似 [Laravel 框架的服务提供者]的例子,感兴趣的同学可以前去翻看。 250 | 251 | 运行方法: 252 | 253 | $ cd /path/to/soliphp/di/ 254 | $ composer install 255 | $ php examples/index.php 256 | 257 | ## API 参考 258 | 259 | [API 参考] 260 | 261 | ## 测试 262 | 263 | $ cd /path/to/soliphp/di/ 264 | $ composer install 265 | $ phpunit 266 | 267 | ## License 268 | 269 | MIT Public License 270 | 271 | [依赖注入与服务定位器]: https://docs.phalconphp.com/en/latest/di 272 | [服务容器]: https://laravel.com/docs/master/container 273 | [API 参考]: http://api.soliphp.com/Soli/Di.html 274 | [examples]: examples 275 | [Laravel 框架的服务提供者]: https://laravel.com/docs/5.4/providers 276 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soliphp/di", 3 | "description": "Soli Dependency Injection Container", 4 | "type": "library", 5 | "keywords": [ 6 | "di", 7 | "container", 8 | "DependencyInjection", 9 | "soliphp" 10 | ], 11 | "homepage": "https://github.com/soliphp/di", 12 | "support": { 13 | "issues": "https://github.com/soliphp/di/issues", 14 | "source": "https://github.com/soliphp/di" 15 | }, 16 | "license": "MIT", 17 | "authors": [ 18 | { 19 | "name": "ueaner", 20 | "email": "ueaner@gmail.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.2", 25 | "psr/container": "^2.0" 26 | }, 27 | "provide": { 28 | "psr/container-implementation": "^1.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Soli\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Soli\\Tests\\": "tests/" 38 | } 39 | }, 40 | "require-dev": { 41 | "phpunit/phpunit": "^11.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Di/Container.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Soli\Di; 7 | 8 | /** 9 | * 依赖注入容器 10 | * 11 | * 依赖注入容器的目的为了降低代码的耦合度,提高应用的可维护性。 12 | * 把组件之间的依赖,转换为对容器的依赖,通过容器 13 | * 进行服务管理(创建、配置和定位)。 14 | * 15 | * @implements ContainerInterface, \ArrayAccess 16 | */ 17 | class Container implements ContainerInterface, \ArrayAccess 18 | { 19 | /** 20 | * 存储容器对象实例 21 | * 22 | * @var \Soli\Di\ContainerInterface 23 | */ 24 | public static $instance; 25 | 26 | /** 27 | * 存储所有注册的服务 28 | * 29 | * @var array 30 | */ 31 | protected $services = []; 32 | 33 | /** 34 | * 存储共享服务实例 35 | * 36 | * @var array 37 | */ 38 | protected $sharedInstances = []; 39 | 40 | /** 41 | * 别名列表 42 | * 43 | * @var array 44 | */ 45 | public $aliases = []; 46 | 47 | /** 48 | * 初始化容器默认实例 49 | */ 50 | public function __construct() 51 | { 52 | if (is_null(static::$instance)) { 53 | static::$instance = $this; // @codeCoverageIgnore 54 | } 55 | return static::$instance; 56 | } 57 | 58 | /** 59 | * 获取容器对象实例 60 | * 61 | * @return \Soli\Di\ContainerInterface 62 | */ 63 | public static function instance(): ContainerInterface 64 | { 65 | if (is_null(static::$instance)) { 66 | static::$instance = new static(); // @codeCoverageIgnore 67 | } 68 | return static::$instance; 69 | } 70 | 71 | /** 72 | * 注册一个服务到容器 73 | * 74 | * @param string $id 服务标识 75 | * @param mixed $definition 服务定义 76 | * @param bool $shared 是否为共享实例,默认为共享实例 77 | * @return \Soli\Di\ServiceInterface 78 | */ 79 | public function set(string $id, mixed $definition, bool $shared = true): ServiceInterface 80 | { 81 | unset($this->sharedInstances[$id], $this->aliases[$id]); 82 | 83 | $service = new Service($id, $definition, $shared); 84 | $this->services[$id] = $service; 85 | return $service; 86 | } 87 | 88 | /** 89 | * 从容器中获取一个服务 90 | * 91 | * 当传入未注册为服务标识的类名时,自动将类名注册为服务,并返回类实例 92 | * 93 | * @param string $id 服务标识|类名|别名 94 | * @param array $parameters 参数 95 | * @return mixed 96 | */ 97 | public function get(string $id, array $parameters = []): mixed 98 | { 99 | $id = $this->getAliasId($id); 100 | 101 | // 如果是共享实例已解析,则返回 102 | if (isset($this->sharedInstances[$id])) { 103 | return $this->sharedInstances[$id]; 104 | } 105 | 106 | if (isset($this->services[$id])) { 107 | /** @var \Soli\Di\ServiceInterface $service 服务实例 */ 108 | $service = $this->services[$id]; 109 | } elseif (class_exists($id)) { 110 | // 自动将类名注册为服务 111 | $service = $this->set($id, $id); 112 | } else { 113 | throw new \InvalidArgumentException("Service '$id' wasn't found in the dependency injection container"); 114 | } 115 | 116 | // 解析服务, 返回服务定义的执行结果 117 | $instance = $service->resolve($parameters, $this); 118 | 119 | // 当前服务实现了 ContainerAwareInterface 接口时,自动为其设置容器 120 | if ($instance instanceof ContainerAwareInterface) { 121 | $instance->setContainer($this); 122 | } 123 | 124 | // 保存到共享实例列表 125 | if ($service->isShared()) { 126 | $this->sharedInstances[$id] = $instance; 127 | } 128 | 129 | return $instance; 130 | } 131 | 132 | /** 133 | * 为某个服务定义别名,主要用于类型提示(接口)的自动注入 134 | * 135 | * @param string $alias 别名(接口名) 136 | * @param string $id 服务标识|类名 137 | * @return void 138 | */ 139 | public function alias(string $alias, string $id): void 140 | { 141 | $this->aliases[$alias] = $id; 142 | } 143 | 144 | /** 145 | * 获取某个别名对应的服务标识 146 | * 147 | * @param string $alias 别名 148 | * @return string 149 | */ 150 | public function getAliasId(string $alias): string 151 | { 152 | if (!isset($this->aliases[$alias])) { 153 | return $alias; 154 | } 155 | 156 | if ($this->aliases[$alias] === $alias) { 157 | throw new \LogicException("[{$alias}] is aliased to itself."); 158 | } 159 | 160 | return $this->getAliasId($this->aliases[$alias]); 161 | } 162 | 163 | /** 164 | * 查询容器中是否存在某个服务 165 | * 166 | * @param string $id 服务标识 167 | * @return bool 168 | */ 169 | public function has(string $id): bool 170 | { 171 | return isset($this->services[$id]) || isset($this->aliases[$id]); 172 | } 173 | 174 | /** 175 | * 从服务容器中删除一个服务 176 | * 177 | * @param string $id 服务标识 178 | * @return void 179 | */ 180 | public function remove(string $id): void 181 | { 182 | unset($this->services[$id]); 183 | unset($this->sharedInstances[$id]); 184 | unset($this->aliases[$id]); 185 | } 186 | 187 | /** 188 | * 清空容器 189 | * 190 | * @return void 191 | */ 192 | public function clear(): void 193 | { 194 | $this->services = []; 195 | $this->sharedInstances = []; 196 | $this->aliases = []; 197 | } 198 | 199 | // 实现 \ArrayAccess 方法 200 | 201 | public function offsetExists($id): bool 202 | { 203 | return $this->has($id); 204 | } 205 | 206 | public function offsetGet($id): mixed 207 | { 208 | return $this->get($id); 209 | } 210 | 211 | public function offsetSet($id, $definition): void 212 | { 213 | $this->set($id, $definition); 214 | } 215 | 216 | public function offsetUnset($id): void 217 | { 218 | $this->remove($id); 219 | } 220 | 221 | /** 222 | * 允许将服务标识作为属性名访问 223 | * 224 | *
225 |      * $container->someService;
226 |      *
227 | * 228 | * @param string $id 服务标识 229 | * @return mixed 230 | */ 231 | public function __get(string $id): mixed 232 | { 233 | return $this->get($id); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Di/ContainerAwareInterface.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Soli\Di; 7 | 8 | /** 9 | * 依赖注入容器感知接口 10 | */ 11 | interface ContainerAwareInterface 12 | { 13 | /** 14 | * 设置依赖注入容器 15 | * 16 | * @param \Soli\Di\ContainerInterface $container 容器对象实例 17 | * @return void 18 | */ 19 | public function setContainer(ContainerInterface $container): void; 20 | 21 | /** 22 | * 获取依赖注入容器 23 | * 24 | * @return \Soli\Di\ContainerInterface 25 | */ 26 | public function getContainer(): ContainerInterface; 27 | } 28 | -------------------------------------------------------------------------------- /src/Di/ContainerAwareTrait.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Soli\Di; 7 | 8 | /** 9 | * ContainerAwareTrait 10 | */ 11 | trait ContainerAwareTrait 12 | { 13 | /** 14 | * @var \Soli\Di\ContainerInterface 15 | */ 16 | protected $container; 17 | 18 | /** 19 | * 设置依赖注入容器 20 | * 21 | * @param \Soli\Di\ContainerInterface $container 容器对象实例 22 | * @return void 23 | */ 24 | public function setContainer(ContainerInterface $container): void 25 | { 26 | $this->container = $container; 27 | } 28 | 29 | /** 30 | * 获取依赖注入容器 31 | * 32 | * @return \Soli\Di\ContainerInterface 33 | */ 34 | public function getContainer(): ContainerInterface 35 | { 36 | return $this->container; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Di/ContainerInterface.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Soli\Di; 7 | 8 | use Psr\Container\ContainerInterface as PsrContainerInterface; 9 | 10 | /** 11 | * ContainerInterface. 12 | */ 13 | interface ContainerInterface extends PsrContainerInterface 14 | { 15 | /** 16 | * 注册一个服务到容器 17 | * 18 | * @param string $id 服务标识 19 | * @param mixed $definition 服务定义 20 | * @param bool $shared 21 | * @return \Soli\Di\ServiceInterface 22 | */ 23 | public function set(string $id, mixed $definition, bool $shared = true): ServiceInterface; 24 | 25 | /** 26 | * 从容器中获取一个服务 27 | * 28 | * 当传入未注册为服务标识的类名时,自动将类名注册为服务,并返回类实例 29 | * 30 | * @param string $id 服务标识|类名 31 | * @return mixed 32 | */ 33 | public function get(string $id): mixed; 34 | 35 | /** 36 | * 为服务添加别名 37 | * 38 | * @param string $alias 39 | * @param string $abstract 40 | * @return void 41 | */ 42 | public function alias(string $alias, string $abstract): void; 43 | 44 | /** 45 | * 查询容器中是否存在某个服务 46 | * 47 | * @param string $id 服务标识 48 | * @return bool 49 | */ 50 | public function has(string $id): bool; 51 | 52 | /** 53 | * 从服务容器中删除一个服务 54 | * 55 | * @param string $id 服务标识 56 | * @return void 57 | */ 58 | public function remove(string $id): void; 59 | 60 | /** 61 | * 清空容器 62 | * 63 | * @return void 64 | */ 65 | public function clear(): void; 66 | } 67 | -------------------------------------------------------------------------------- /src/Di/Service.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Soli\Di; 7 | 8 | use Closure; 9 | use ReflectionClass; 10 | use ReflectionFunction; 11 | use ReflectionParameter; 12 | 13 | /** 14 | * 服务原型 15 | * 16 | * 容器中对单个服务单元的定义 17 | */ 18 | class Service implements ServiceInterface 19 | { 20 | /** 21 | * 服务标识 22 | * 23 | * @var string $id 24 | */ 25 | protected $id; 26 | 27 | /** 28 | * 服务定义, 对象实例(包括Closure)|类名 29 | * 30 | * @var object|string 31 | */ 32 | protected $definition; 33 | 34 | /** 35 | * 是否为共享服务 36 | * 37 | * @var bool 38 | */ 39 | protected $shared = false; 40 | 41 | /** 42 | * 传入的参数 43 | * 44 | * @var array 45 | */ 46 | protected $parameters; 47 | 48 | /** 49 | * 容器实例 50 | * 51 | * @var \Soli\Di\ContainerInterface; 52 | */ 53 | protected $container; 54 | 55 | /** 56 | * Service constructor. 57 | * 58 | * @param string $id 服务标识 59 | * @param object|string $definition 60 | * @param bool $shared 61 | */ 62 | public function __construct(string $id, object|string $definition, bool $shared = true) 63 | { 64 | $this->id = $id; 65 | $this->definition = $definition; 66 | $this->shared = $shared; 67 | } 68 | 69 | /** 70 | * 服务是否为共享的 71 | * 72 | * @return bool 73 | */ 74 | public function isShared(): bool 75 | { 76 | return $this->shared; 77 | } 78 | 79 | /** 80 | * 解析服务 81 | * 82 | * @param array $parameters 参数 83 | * @param \Soli\Di\ContainerInterface $container 容器对象实例 84 | * @return mixed 85 | * @throws \Exception 86 | */ 87 | public function resolve(array $parameters = [], ?ContainerInterface $container = null): mixed 88 | { 89 | $this->parameters = $parameters; 90 | $this->container = $container; 91 | 92 | // 创建实例 93 | $instance = null; 94 | $definition = $this->definition; 95 | $type = gettype($definition); 96 | 97 | switch ($type) { 98 | case 'object': 99 | if ($definition instanceof Closure) { 100 | $instance = $this->buildClosure(); 101 | } else { 102 | // 对象实例 103 | $instance = $definition; 104 | } 105 | break; 106 | case 'string': 107 | // 已存在的类名 108 | $instance = $this->buildClass(); 109 | break; 110 | default: 111 | throw new \DomainException("Service '{$this->id}' cannot be resolved"); 112 | } 113 | 114 | return $instance; 115 | } 116 | 117 | /** 118 | * @return mixed 119 | * @throws \Exception 120 | */ 121 | protected function buildClosure(): mixed 122 | { 123 | $container = $this->container; 124 | $closure = $this->definition; 125 | 126 | // 匿名函数内使用 $this 访问容器中的其他服务 127 | if (is_object($container)) { 128 | $closure = $closure->bindTo($container); 129 | } 130 | 131 | $reflector = new ReflectionFunction($closure); 132 | 133 | // ReflectionParameter[] 134 | $dependencies = $reflector->getParameters(); 135 | 136 | $instances = $this->resolveDependencies( 137 | $dependencies 138 | ); 139 | 140 | return $closure(...$instances); 141 | } 142 | 143 | /** 144 | * @return object 145 | * @throws \Exception 146 | */ 147 | protected function buildClass(): ?object 148 | { 149 | $className = $this->definition; 150 | 151 | if (!class_exists($className)) { 152 | throw new \DomainException("Service '{$this->id}' cannot be resolved"); 153 | } 154 | 155 | $reflector = new ReflectionClass($className); 156 | 157 | if (!$reflector->isInstantiable()) { 158 | throw new \DomainException("Can not instantiate {$reflector->name}"); 159 | } 160 | 161 | // ReflectionMethod 162 | $constructor = $reflector->getConstructor(); 163 | if (is_null($constructor)) { 164 | return $reflector->newInstance(); 165 | } 166 | 167 | // ReflectionParameter[] 168 | $dependencies = $constructor->getParameters(); 169 | 170 | $instances = $this->resolveDependencies( 171 | $dependencies 172 | ); 173 | 174 | return $reflector->newInstanceArgs($instances); 175 | } 176 | 177 | /** 178 | * @param ReflectionParameter[] $dependencies 179 | * @return array 180 | * @throws \Exception 181 | */ 182 | protected function resolveDependencies(array $dependencies): array 183 | { 184 | $parameters = $this->parameters; 185 | 186 | $results = []; 187 | 188 | foreach ($dependencies as $dependency) { 189 | // 优先使用传入的参数值 190 | if (isset($parameters[$dependency->name])) { 191 | $results[] = $parameters[$dependency->name]; 192 | continue; 193 | } 194 | 195 | $results[] = is_null($this->getParameterClassName($dependency)) 196 | ? $this->resolvePrimitive($dependency) 197 | : $this->resolveClass($dependency); 198 | } 199 | 200 | return $results; 201 | } 202 | 203 | /** 204 | * Get the class name of the given parameter's type, if possible. 205 | * from https://github.com/illuminate/container/blob/d3295a124cd01e901bd734eb0e26fc2b03083f83/Util.php#L51 206 | * 207 | * From Reflector::getParameterClassName() in Illuminate\Support. 208 | * 209 | * @param \ReflectionParameter $parameter 210 | * @return string|null 211 | */ 212 | protected function getParameterClassName(ReflectionParameter $parameter): ?string 213 | { 214 | $type = $parameter->getType(); 215 | 216 | if (! $type instanceof \ReflectionNamedType || $type->isBuiltin()) { 217 | return null; 218 | } 219 | 220 | $name = $type->getName(); 221 | 222 | if (! is_null($class = $parameter->getDeclaringClass())) { 223 | if ($name === 'self') { 224 | return $class->getName(); 225 | } 226 | 227 | if ($name === 'parent' && $parent = $class->getParentClass()) { 228 | return $parent->getName(); 229 | } 230 | } 231 | 232 | return $name; 233 | } 234 | 235 | /** 236 | * @param \ReflectionParameter $parameter 237 | * @return mixed 238 | * @throws \Exception 239 | */ 240 | protected function resolvePrimitive(ReflectionParameter $parameter): mixed 241 | { 242 | if ($parameter->isDefaultValueAvailable()) { // false 243 | return $parameter->getDefaultValue(); 244 | } 245 | 246 | $message = sprintf( 247 | "Unresolvable dependency resolving [%s] in class %s", 248 | $parameter, 249 | $parameter->getDeclaringClass()->getName() 250 | ); 251 | 252 | throw new \DomainException($message); 253 | } 254 | 255 | /** 256 | * @param \ReflectionParameter $parameter 257 | * @return mixed 258 | * @throws \Exception 259 | */ 260 | protected function resolveClass(ReflectionParameter $parameter): mixed 261 | { 262 | try { 263 | return $this->container->get(strval($parameter->getType())); 264 | } catch (\Exception $e) { 265 | if ($parameter->isOptional()) { 266 | return $parameter->getDefaultValue(); 267 | } 268 | 269 | throw $e; 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/Di/ServiceInterface.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Soli\Di; 7 | 8 | /** 9 | * ServiceInterface. 10 | */ 11 | interface ServiceInterface 12 | { 13 | /** 14 | * 解析服务 15 | * 16 | * @param array $parameters 参数 17 | * @param \Soli\Di\ContainerInterface $container 容器对象实例 18 | * @return mixed 19 | */ 20 | public function resolve(array $parameters = [], ?ContainerInterface $container = null): mixed; 21 | 22 | /** 23 | * 服务是否为共享的 24 | * 25 | * @return bool 26 | */ 27 | public function isShared(): bool; 28 | } 29 | --------------------------------------------------------------------------------