Repository navigation
Expand file tree
/
Copy pathServiceProvider.php
More file actions
278 lines (257 loc) · 10.1 KB
/
Copy pathServiceProvider.php
File metadata and controls
278 lines (257 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<?php
declare(strict_types=1);
namespace Loom;
/**
* 根容器,由 ServiceCollection::build() 生成,对应 ASP.NET Core 的根 IServiceProvider 加 IServiceScopeFactory。
*
* 根容器只持有单例。Scoped 服务必须在 createScope() 得到的 ServiceScope 里解析;在根上解析,或单例构造时依赖它,都会抛出 LogicException,
* 这样单例不会捕获某一次请求的对象。单例第一次创建时持有 Go 侧的锁,并发解析只构造一次。
*
* 构造参数或工厂参数可以声明 ServiceProvider(总是根容器)、ServiceResolver(发起解析的容器)或 ServiceScope(当前作用域)。
*
* Origami 的 HTTP 回调在请求虚拟机里执行,请求里对脚本对象的写入只对这一次请求可见。需要整个进程共用的单例,要在开始监听之前解析出来。
*
* @example $provider = $services->build(); $provider->withScope(fn (ServiceScope $scope) => $scope->get(Clock::class)->now());
*/
final class ServiceProvider implements ServiceResolver
{
/** @var array<string, list<ServiceDescriptor>> */
private array $descriptors;
/** @var array<string, object> */
private array $singletons = [];
/** @var list<Disposable> */
private array $disposables = [];
/** @var array<string, ServiceDescriptor> */
private array $implicit = [];
/** @var array<string, string> */
private array $resolving = [];
private bool $disposed = false;
/**
* 由 ServiceCollection::build() 调用。
*
* @param array<string, list<ServiceDescriptor>> $descriptors 冻结后的登记表。
*/
public function __construct(array $descriptors)
{
$this->descriptors = $descriptors;
}
public function get(string $id): object
{
return $this->resolve($id, null);
}
public function tryGet(string $id): ?object
{
return $this->has($id) ? $this->get($id) : null;
}
public function getAll(string $id): array
{
return $this->resolveAll($id, null);
}
public function has(string $id): bool
{
return isset($this->descriptors[$id]) || $id === self::class || $id === ServiceResolver::class;
}
/**
* 新建作用域。调用方负责 dispose(),或改用 withScope()。
*/
public function createScope(): ServiceScope
{
$this->assertActive();
return new ServiceScope($this);
}
/**
* 打开一个作用域,在其中执行一次工作单元,结束时释放作用域。回调收到这个 ServiceScope,返回值原样返回。
* 作用域里的模型静态查询使用该作用域的 Database。
*
* @example $count = $provider->withScope(static fn (ServiceScope $scope): int => Task::query()->count());
*/
public function withScope(callable $operation): mixed
{
$scope = $this->createScope();
try {
$result = $scope->run($operation);
} catch (\Throwable $error) {
try {
$scope->dispose();
} catch (\Throwable) {
}
throw $error;
}
$scope->dispose();
return $result;
}
/**
* 打开作用域、绑定 Monitoring 会话、执行回调,结束时释放作用域(Monitoring 随作用域 dispose 收尾)。
*
* @example ScopeMonitoring::run($provider, MonitoringContext::timer($class), static fn (ServiceScope $scope) => ...);
*/
public function withMonitoredScope(\Loom\Monitoring\MonitoringContext $context, callable $operation): mixed
{
return \Loom\Monitoring\ScopeMonitoring::run($this, $context, $operation);
}
/**
* 按创建的相反顺序释放容器创建的单例。之后不能再解析服务。重复调用直接返回。
*/
public function dispose(): void
{
if ($this->disposed) {
return;
}
$this->disposed = true;
$disposables = $this->disposables;
$this->disposables = [];
$this->singletons = [];
$failure = null;
foreach (array_reverse($disposables) as $service) {
try {
$service->dispose();
} catch (\Throwable $error) {
$failure ??= $error;
}
}
if ($failure !== null) {
throw $failure;
}
}
/**
* @internal 由 ServiceScope 调用。$scope 为 null 表示在根上解析。
*/
public function resolve(string $id, ?ServiceScope $scope): object
{
$this->assertActive();
if ($id === self::class) {
return $this;
}
if ($id === ServiceResolver::class) {
return $scope ?? $this;
}
if ($id === ServiceScope::class) {
if ($scope === null) {
throw new \LogicException('ServiceScope can only be resolved inside a scope');
}
return $scope;
}
$descriptors = $this->descriptors[$id] ?? null;
if ($descriptors === null) {
return $this->resolveDescriptor($this->implicit($id), $id . '#implicit', $scope);
}
$index = count($descriptors) - 1;
return $this->resolveDescriptor($descriptors[$index], $id . '#' . $index, $scope);
}
/**
* @internal 由 ServiceScope 调用。
*
* @return list<object>
*/
public function resolveAll(string $id, ?ServiceScope $scope): array
{
$this->assertActive();
$services = [];
foreach ($this->descriptors[$id] ?? [] as $index => $descriptor) {
$services[] = $this->resolveDescriptor($descriptor, $id . '#' . $index, $scope);
}
return $services;
}
/**
* @internal 按登记创建对象:类名走构造注入,工厂收到发起解析的容器。随后注入 InjectConfig 属性并检查类型。
*/
public static function activate(ServiceDescriptor $descriptor, ServiceResolver $services): object
{
$implementation = $descriptor->implementation ?? $descriptor->id;
$instance = is_string($implementation)
? ConfigInjector::create($implementation, $services)
: $implementation($services);
if (!is_object($instance)) {
throw new \LogicException('Service factory must return an object: ' . $descriptor->id);
}
if ((class_exists($descriptor->id) || interface_exists($descriptor->id)) && !($instance instanceof $descriptor->id)) {
throw new \LogicException('Service must implement ' . $descriptor->id);
}
ConfigInjector::inject($instance, $services);
return $instance;
}
/**
* @internal 记录正在创建的服务,发现循环依赖时抛出 LogicException。
*/
public static function guard(array &$resolving, string $key, string $id, callable $create): object
{
if (isset($resolving[$key])) {
throw new \LogicException('Dependency cycle: ' . implode(' -> ', array_values($resolving)) . ' -> ' . $id);
}
$resolving[$key] = $id;
try {
return $create();
} finally {
unset($resolving[$key]);
}
}
private function resolveDescriptor(ServiceDescriptor $descriptor, string $key, ?ServiceScope $scope): object
{
if ($descriptor->instance !== null) {
return $descriptor->instance;
}
// 按 value 比较:Origami 请求虚拟机里,主虚拟机创建的枚举单例与请求侧的 ServiceLifetime::Scoped 可能不是同一实例,=== 会失败并误走 transient。
if ($descriptor->lifetime->value === ServiceLifetime::Singleton->value) {
return $this->singleton($descriptor, $key);
}
if ($descriptor->lifetime->value === ServiceLifetime::Scoped->value) {
if ($scope === null) {
throw new \LogicException($this->resolving === []
? 'Cannot resolve scoped service ' . $descriptor->id . ' from the root provider; create a scope first'
: 'Singleton ' . end($this->resolving) . ' cannot depend on scoped service ' . $descriptor->id);
}
return $scope->scoped($descriptor, $key);
}
if ($scope !== null) {
return $scope->transient($descriptor, $key);
}
$provider = $this;
$instance = self::guard($this->resolving, $key, $descriptor->id, static fn (): object => self::activate($descriptor, $provider));
if ($instance instanceof Disposable) {
$this->disposables[] = $instance;
}
return $instance;
}
private function singleton(ServiceDescriptor $descriptor, string $key): object
{
if (isset($this->singletons[$key])) {
return $this->singletons[$key];
}
$provider = $this;
return \Loom\Native\withSingletonLock($this, static function () use ($provider, $descriptor, $key): object {
return $provider->createSingleton($descriptor, $key);
});
}
/**
* @internal 只在单例锁内调用。
*/
public function createSingleton(ServiceDescriptor $descriptor, string $key): object
{
if (isset($this->singletons[$key])) {
return $this->singletons[$key];
}
$provider = $this;
$instance = self::guard($this->resolving, $key, $descriptor->id, static fn (): object => self::activate($descriptor, $provider));
$this->singletons[$key] = $instance;
if ($instance instanceof Disposable) {
$this->disposables[] = $instance;
}
return $instance;
}
private function implicit(string $id): ServiceDescriptor
{
if (isset($this->implicit[$id])) {
return $this->implicit[$id];
}
if (!class_exists($id) || !(new \ReflectionClass($id))->isInstantiable()) {
throw new \LogicException('Service is not registered: ' . $id);
}
return $this->implicit[$id] = ServiceDescriptor::scoped($id);
}
private function assertActive(): void
{
if ($this->disposed) {
throw new \LogicException('The service provider has been disposed');
}
}
}