Repository navigation
Expand file tree
/
Copy pathServiceScope.php
More file actions
139 lines (125 loc) · 4.38 KB
/
Copy pathServiceScope.php
File metadata and controls
139 lines (125 loc) · 4.38 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
<?php
declare(strict_types=1);
namespace Loom;
/**
* 一个工作单元的服务作用域,对应 ASP.NET Core 的 IServiceScope。宿主为每次 HTTP 请求、TCP 消息、gRPC 调用、命令、队列节拍和定时器节拍各建一个。
*
* Scoped 服务在这里各建一份,作用域释放时按创建的相反顺序释放其中的 Disposable。单例从根容器取。
* 作用域不拥有数据库连接:Database 只保存这次工作里的事务和命名连接,连接池在进程级。
*
* @example $scope = $provider->createScope(); try { $scope->run(fn () => Task::query()->count()); } finally { $scope->dispose(); }
*/
final class ServiceScope implements ServiceResolver
{
/** @var array<string, object> */
private array $instances = [];
/** @var list<Disposable> */
private array $disposables = [];
/** @var array<string, string> */
private array $resolving = [];
private bool $disposed = false;
public function __construct(private readonly ServiceProvider $provider)
{
}
/**
* 创建这个作用域的根容器。
*/
public function provider(): ServiceProvider
{
return $this->provider;
}
public function get(string $id): object
{
$this->assertActive();
return $this->provider->resolve($id, $this);
}
public function tryGet(string $id): ?object
{
return $this->has($id) ? $this->get($id) : null;
}
public function getAll(string $id): array
{
$this->assertActive();
return $this->provider->resolveAll($id, $this);
}
public function has(string $id): bool
{
return $id === self::class || $this->provider->has($id);
}
/**
* 在这个作用域里执行回调。登记了 Database 时,回调期间模型的静态查询(Model::query() 等)使用本作用域的 Database。
* 绑定挂在本次调用的执行帧上,回调返回或抛错后恢复。
*
* @param callable $operation 接收本作用域。
* @return mixed 回调的返回值。
*/
public function run(callable $operation): mixed
{
$this->assertActive();
$scope = $this;
if (!$this->provider->has(Database::class)) {
return $operation($scope);
}
return ModelContext::during($this->get(Database::class), static function () use ($operation, $scope): mixed {
return $operation($scope);
});
}
/**
* 按创建的相反顺序释放本作用域创建的 Disposable。某个释放失败时其余仍会释放,最后抛出第一个异常。重复调用直接返回。
*/
public function dispose(): void
{
if ($this->disposed) {
return;
}
$this->disposed = true;
$disposables = $this->disposables;
$this->disposables = [];
$this->instances = [];
$failure = null;
foreach (array_reverse($disposables) as $service) {
try {
$service->dispose();
} catch (\Throwable $error) {
$failure ??= $error;
}
}
if ($failure !== null) {
throw $failure;
}
}
/**
* @internal 由 ServiceProvider 调用。
*/
public function scoped(ServiceDescriptor $descriptor, string $key): object
{
if (isset($this->instances[$key])) {
return $this->instances[$key];
}
$scope = $this;
$instance = ServiceProvider::guard($this->resolving, $key, $descriptor->id, static fn (): object => ServiceProvider::activate($descriptor, $scope));
$this->instances[$key] = $instance;
if ($instance instanceof Disposable) {
$this->disposables[] = $instance;
}
return $instance;
}
/**
* @internal 由 ServiceProvider 调用。
*/
public function transient(ServiceDescriptor $descriptor, string $key): object
{
$scope = $this;
$instance = ServiceProvider::guard($this->resolving, $key, $descriptor->id, static fn (): object => ServiceProvider::activate($descriptor, $scope));
if ($instance instanceof Disposable) {
$this->disposables[] = $instance;
}
return $instance;
}
private function assertActive(): void
{
if ($this->disposed) {
throw new \LogicException('The service scope has been disposed');
}
}
}