Passed
Push — master ( 86fa88...ed1da9 )
by teng
01:20
created

Container::replaceInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 11
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
 * @copyright (C) 2019 pokerapp.cn
4
 * @license   https://pokerapp.cn/license
5
 */
6
namespace poker\container;
7
8
use Closure;
9
use ReflectionClass;
10
use ReflectionMethod;
11
use ReflectionFunction;
12
use ReflectionParameter;
13
use poker\container\traits\ContainerAwareTrait;
14
use poker\container\exceptions\ContainerException;
15
use poker\container\exceptions\UnableToInstantiateException;
16
use poker\container\exceptions\UnableToResolveParameterException;
17
18
use function is_int;
19
use function vsprintf;
20
use function is_array;
21
use function array_merge;
22
use function array_replace;
23
use function array_values;
24
25
/**
26
 * 控制容器的反转
27
 *
28
 * @author Levine <[email protected]>
29
 */
30
class Container
31
{
32
	/**
33
	 * 容器自身单例实例
34
	 *
35
	 * @var \poker\container\Container
36
	 */
37
	protected static $instance;
38
39
	/**
40
	 * 注册的类型提示
41
	 *
42
	 * @var array
43
	 */
44
	protected $hints = [];
45
46
	/**
47
	 * 别名
48
	 *
49
	 * @var array
50
	 */
51
	protected $aliases = [];
52
53
	/**
54
	 * 单例实例
55
	 *
56
	 * @var array
57
	 */
58
	protected $instances = [];
59
60
	/**
61
	 * 实例替换程序
62
	 *
63
	 * @var array
64
	 */
65
	protected $replacers = [];
66
67
	/**
68
	 * 上下文依赖关系
69
	 *
70
	 * @var array
71
	 */
72
	protected $dependencies = [];
73
74
	/**
75
	 * 返回容器自身单例实例
76
	 *
77
	 * @return \poker\container\Container
78
	 */
79
	public static function getInstance()
80
	{
81
		if (null === static::$instance) {
82
			static::$instance = new static;
83
		}
84
85
		return static::$instance;
86
	}
87
88
	/**
89
	 * 检查是否在容器中注册了类
90
	 *
91
	 * @param  string $class 类名
92
	 * @return bool
93
	 */
94
	public function has(string $class): bool
95
	{
96
		$class = $this->resolveAlias($class);
97
98
		return (isset($this->hints[$class]) || isset($this->instances[$class]));
99
	}
100
101
	/**
102
	 * 如果一个类已注册为单例则返回TRUE,否则返回FALSE
103
	 *
104
	 * @param  string $class 类名
105
	 * @return bool
106
	 */
107
	public function isSingleton(string $class): bool
108
	{
109
		$class = $this->resolveAlias($class);
110
111
		return isset($this->instances[$class]) || (isset($this->hints[$class]) && $this->hints[$class]['singleton'] === true);
112
	}
113
114
	/**
115
	 * 即使该类已注册为单例,也返回一个新的类实例
116
	 *
117
	 * @param  string $class      类名
118
	 * @param  array  $parameters 构造函数参数
119
	 * @return object
120
	 */
121
	public function getFresh(string $class, array $parameters = []): object
122
	{
123
		return $this->get($class, $parameters, false);
124
	}
125
126
	/**
127
	 * 注册一个类型提示
128
	 *
129
	 * @param string|array    $hint      包含类型提示和别名的类型提示或数组
130
	 * @param string|\Closure $class     类名或闭包
131
	 * @param bool            $singleton 是否每次都应该返回相同的实例
132
	 */
133
	public function register($hint, $class, bool $singleton = false): void
134
	{
135
		$this->hints[$this->parseHint($hint)] = ['class' => $class, 'singleton' => $singleton];
136
	}
137
138
	/**
139
	 * 注册一个类型提示并每次返回相同的实例
140
	 *
141
	 * @param string|array    $hint  包含类型提示和别名的类型提示或数组
142
	 * @param string|\Closure $class 类名或闭包
143
	 */
144
	public function registerSingleton($hint, $class): void
145
	{
146
		$this->register($hint, $class, true);
147
	}
148
149
	/**
150
	 * 注册一个单例实例
151
	 *
152
	 * @param string|array $hint     包含类型提示和别名的类型提示或数组
153
	 * @param object       $instance 类实例
154
	 */
155
	public function registerInstance($hint, object $instance): void
156
	{
157
		$this->instances[$this->parseHint($hint)] = $instance;
158
	}
159
160
	/**
161
	 * 替换一个已注册的类型提示
162
	 *
163
	 * @param  string                                         $hint      类型提示
164
	 * @param  string|\Closure                                $class     类名或闭包
165
	 * @param  bool                                           $singleton 是否替换一个单例
166
	 * @throws \poker\container\exceptions\ContainerException
167
	 */
168
	public function replace(string $hint, $class, bool $singleton = false): void
169
	{
170
		$hint = $this->resolveAlias($hint);
171
172
		if (!isset($this->hints[$hint])) {
173
			throw new ContainerException(vsprintf('Unable to replace [ %s ] as it hasn\'t been registered.', [$hint]));
174
		}
175
176
		$this->hints[$hint]['class'] = $class;
177
178
		if ($singleton) {
179
			unset($this->instances[$hint]);
180
		}
181
182
		$this->replaceInstances($hint);
183
	}
184
185
	/**
186
	 * 注册替换程序
187
	 *
188
	 * @param string      $hint      类型提示
189
	 * @param callable    $replacer  实例替换程序
190
	 * @param string|null $eventName 事件名称
191
	 */
192
	public function onReplace(string $hint, callable $replacer, ?string $eventName = null): void
193
	{
194
		$hint = $this->resolveAlias($hint);
195
196
		$eventName === null ? ($this->replacers[$hint][] = $replacer) : ($this->replacers[$hint][$eventName] = $replacer);
197
	}
198
199
	/**
200
	 * 替换一个已注册的单例类型提示
201
	 *
202
	 * @param string          $hint  类型提示
203
	 * @param string|\Closure $class 类名或闭包
204
	 */
205
	public function replaceSingleton(string $hint, $class): void
206
	{
207
		$this->replace($hint, $class, true);
208
	}
209
210
	/**
211
	 * 替换一个单例实例
212
	 *
213
	 * @param  string                                         $hint     类型提示
214
	 * @param  object                                         $instance 类实例
215
	 * @throws \poker\container\exceptions\ContainerException
216
	 */
217
	public function replaceInstance(string $hint, object $instance): void
218
	{
219
		$hint = $this->resolveAlias($hint);
220
221
		if (!isset($this->instances[$hint])) {
222
			throw new ContainerException(vsprintf('Unable to replace [ %s ] as it hasn\'t been registered.', [$hint]));
223
		}
224
225
		$this->instances[$hint] = $instance;
226
227
		$this->replaceInstances($hint);
228
	}
229
230
	/**
231
	 * 注册一个上下文依赖关系
232
	 *
233
	 * @param string $class          类
234
	 * @param string $interface      接口
235
	 * @param string $implementation 实现
236
	 */
237
	public function registerContextualDependency(string $class, string $interface, string $implementation): void
238
	{
239
		$this->dependencies[$class][$interface] = $implementation;
240
	}
241
242
	/**
243
	 * 返回一个类实例
244
	 *
245
	 * @param  string $class         类名
246
	 * @param  array  $parameters    构造函数参数
247
	 * @param  bool   $reuseInstance 是否重用现有实例
248
	 * @return object
249
	 */
250
	public function get(string $class, array $parameters = [], bool $reuseInstance = true): object
251
	{
252
		$class = $this->resolveAlias($class);
253
254
		// 如果存在单例实例,直接返回它
255
		if ($reuseInstance && isset($this->instances[$class])) {
256
			return $this->instances[$class];
257
		}
258
259
		// 创建新实例
260
		$instance = $this->factory($this->resolveHint($class), $parameters);
261
262
		// 如果实例注册为单例,则存储该实例
263
		if ($reuseInstance && isset($this->hints[$class]) && $this->hints[$class]['singleton']) {
264
			$this->instances[$class] = $instance;
265
		}
266
267
		// 返回的实例
268
		return $instance;
269
	}
270
271
	/**
272
	 * 工厂创建一个类实例
273
	 *
274
	 * @param  string|\Closure $class      类名或闭包
275
	 * @param  array           $parameters 构造函数参数
276
	 * @return object
277
	 */
278
	public function factory($class, array $parameters = []): object
279
	{
280
		// 实例化类
281
		if ($class instanceof Closure) {
282
			$instance = $this->closureFactory($class, $parameters);
283
		} else {
284
			$instance = $this->reflectionFactory($class, $parameters);
285
		}
286
287
		// 如果类可以识别容器,则使用setter注入容器
288
		if ($this->isContainerAware($instance)) {
289
			$instance->setContainer($this);
290
		}
291
292
		// 返回的实例
293
		return $instance;
294
	}
295
296
	/**
297
	 * 执行可调用并注入其依赖项
298
	 *
299
	 * @param  callable $callable   可调用
300
	 * @param  array    $parameters 参数
301
	 * @return mixed
302
	 */
303
	public function call(callable $callable, array $parameters = [])
304
	{
305
		if (is_array($callable)) {
306
			$reflection = new ReflectionMethod($callable[0], $callable[1]);
307
		} else {
308
			$reflection = new ReflectionFunction($callable);
309
		}
310
311
		return $callable(...$this->resolveParameters($reflection->getParameters(), $parameters));
312
	}
313
314
	/**
315
	 * 使用工厂闭包创建一个类实例
316
	 *
317
	 * @param  \Closure $factory    类名或闭包
318
	 * @param  array    $parameters 构造函数参数
319
	 * @return object
320
	 */
321
	protected function closureFactory(Closure $factory, array $parameters): object
322
	{
323
		// 将容器作为第一个参数传递,然后传递所提供的参数
324
		return $factory(...array_merge([$this], $parameters));
325
	}
326
327
	/**
328
	 * 使用反射创建一个类实例
329
	 *
330
	 * @param  string                                                   $class      类名
331
	 * @param  array                                                    $parameters 构造函数参数
332
	 * @throws \poker\container\exceptions\UnableToInstantiateException
333
	 * @return object
334
	 */
335
	protected function reflectionFactory(string $class, array $parameters): object
336
	{
337
		$class = new ReflectionClass($class);
338
339
		// 检查是否可以实例化该类
340
		if (!$class->isInstantiable()) {
341
			throw new UnableToInstantiateException(vsprintf('Unable to create a [ %s ] instance.', [$class->getName()]));
342
		}
343
344
		// 获取类的构造函数
345
		$constructor = $class->getConstructor();
346
347
		// 如果没有构造函数,只返回一个新实例
348
		if ($constructor === null) {
349
			return $class->newInstance();
350
		}
351
352
		// 该类有一个构造函数,因此将使用解析的参数返回一个新实例
353
		return $class->newInstanceArgs($this->resolveParameters($constructor->getParameters(), $parameters, $class));
354
	}
355
356
	/**
357
	 * 检查类是否可识别容器
358
	 *
359
	 * @param  object $class 类实例
360
	 * @return bool
361
	 */
362
	protected function isContainerAware(object $class): bool
363
	{
364
		$traits = ClassInspector::getTraits($class);
365
366
		return isset($traits[ContainerAwareTrait::class]);
367
	}
368
369
	/**
370
	 * 解析一个类型提示
371
	 *
372
	 * @param  string          $hint 类型提示
373
	 * @return string|\Closure
374
	 */
375
	protected function resolveHint(string $hint)
376
	{
377
		return $this->hints[$hint]['class'] ?? $hint;
378
	}
379
380
	/**
381
	 * 根据别名返回名称。如果不存在别名只返回收到的值
382
	 *
383
	 * @param  string $alias 别名
384
	 * @return string
385
	 */
386
	protected function resolveAlias(string $alias): string
387
	{
388
		return $this->aliases[$alias] ?? $alias;
389
	}
390
391
	/**
392
	 * 解析一个上下文依赖关系
393
	 *
394
	 * @param  string $class     类
395
	 * @param  string $interface 接口
396
	 * @return string
397
	 */
398
	protected function resolveContextualDependency(string $class, string $interface): string
399
	{
400
		return $this->dependencies[$class][$interface] ?? $interface;
401
	}
402
403
	/**
404
	 * 解析提示参数
405
	 *
406
	 * @param  string|array $hint 包含类型提示和别名的类型提示或数组
407
	 * @return string
408
	 */
409
	protected function parseHint($hint): string
410
	{
411
		if (is_array($hint)) {
412
			[$hint, $alias] = $hint;
413
414
			$this->aliases[$alias] = $hint;
415
		}
416
417
		return $hint;
418
	}
419
420
	/**
421
	 * 解析一个参数
422
	 *
423
	 * @param  \ReflectionParameter                                          $parameter 反射参数实例
424
	 * @param  \ReflectionClass|null                                         $class     反射类实例
425
	 * @throws \poker\container\exceptions\UnableToInstantiateException
426
	 * @throws \poker\container\exceptions\UnableToResolveParameterException
427
	 * @return mixed
428
	 */
429
	protected function resolveParameter(ReflectionParameter $parameter, ?ReflectionClass $class = null)
430
	{
431
		// 如果参数是类实例,将尝试使用容器解析它
432
		if (($parameterClass = $parameter->getClass()) !== null) {
433
			$parameterClassName = $parameterClass->getName();
434
435
			if ($class !== null) {
436
				$parameterClassName = $this->resolveContextualDependency($class->getName(), $parameterClassName);
437
			}
438
439
			try {
440
				return $this->get($parameterClassName);
441
			} catch(UnableToInstantiateException | UnableToResolveParameterException $e) {
442
				if ($parameter->allowsNull()) {
443
					return null;
444
				}
445
446
				throw $e;
447
			}
448
		}
449
450
		// 如果参数有一个默认值,将使直接使用它
451
		if ($parameter->isDefaultValueAvailable()) {
452
			return $parameter->getDefaultValue();
453
		}
454
455
		// 该参数可以为空,因此只返回null
456
		if ($parameter->hasType() && $parameter->allowsNull()) {
457
			return null;
458
		}
459
460
		// 如果上述条件都不成立,则抛出异常消息
461
		throw new UnableToResolveParameterException(vsprintf('Unable to resolve the [ $%s ] parameter of [ %s ].', [$parameter->getName(), $this->getDeclaringFunction($parameter)]));
462
	}
463
464
	/**
465
	 * 解析多个参数
466
	 *
467
	 * @param  array                 $reflectionParameters 反射参数
468
	 * @param  array                 $providedParameters   提供的参数
469
	 * @param  \ReflectionClass|null $class                反射类实例
470
	 * @return array
471
	 */
472
	protected function resolveParameters(array $reflectionParameters, array $providedParameters, ?ReflectionClass $class = null): array
473
	{
474
		if (empty($reflectionParameters)) {
475
			return array_values($providedParameters);
476
		}
477
478
		// 将提供的参数与使用反射得到的参数合并
479
		$parameters = $this->mergeParameters($reflectionParameters, $providedParameters);
480
481
		// 循环遍历参数并处理需要解析的参数
482
		foreach ($parameters as $key => $parameter) {
483
			if ($parameter instanceof ReflectionParameter) {
484
				$parameters[$key] = $this->resolveParameter($parameter, $class);
485
			}
486
		}
487
488
		// 返回解析的参数
489
		return array_values($parameters);
490
	}
491
492
	/**
493
	 * 将提供的参数与反射参数合并
494
	 *
495
	 * @param  array $reflectionParameters 反射参数
496
	 * @param  array $providedParameters   提供的参数
497
	 * @return array
498
	 */
499
	protected function mergeParameters(array $reflectionParameters, array $providedParameters): array
500
	{
501
		// 使反射参数数组关联
502
		$associativeReflectionParameters = [];
503
		foreach ($reflectionParameters as $value) {
504
			$associativeReflectionParameters[$value->getName()] = $value;
505
		}
506
507
		// 使提供的参数数组关联
508
		$associativeProvidedParameters = [];
509
		foreach ($providedParameters as $key => $value) {
510
			if (is_int($key)) {
511
				$associativeProvidedParameters[$reflectionParameters[$key]->getName()] = $value;
512
			} else {
513
				$associativeProvidedParameters[$key] = $value;
514
			}
515
		}
516
517
		// 返回合并后的参数
518
		return array_replace($associativeReflectionParameters, $associativeProvidedParameters);
519
	}
520
521
	/**
522
	 * 返回声明函数的名称
523
	 *
524
	 * @param  \ReflectionParameter $parameter 反射参数实例
525
	 * @return string
526
	 */
527
	protected function getDeclaringFunction(ReflectionParameter $parameter): string
528
	{
529
		$declaringFunction = $parameter->getDeclaringFunction();
530
531
		if ($declaringFunction->isClosure()) {
532
			return 'Closure';
533
		}
534
535
		if (($class = $parameter->getDeclaringClass()) === null) {
536
			return $declaringFunction->getName();
537
		}
538
539
		return $class->getName() . '::' . $declaringFunction->getName();
540
	}
541
542
	/**
543
	 * 替换以前解析的实例
544
	 *
545
	 * @param string $hint 类型提示
546
	 */
547
	protected function replaceInstances(string $hint): void
548
	{
549
		if (isset($this->replacers[$hint])) {
550
			$instance = $this->get($hint);
551
552
			foreach ($this->replacers[$hint] as $replacer) {
553
				$replacer($instance);
554
			}
555
		}
556
	}
557
}
558