sz_rust_core/container/mod.rs
1//! App 容器 — DB/Cache/Log 单例 + DI 服务容器
2//!
3//! 对齐 PHP `app()` 容器,持有全局配置、单例和服务绑定。
4//!
5//! ## 设计
6//!
7//! - 基于 `OnceCell` 实现全局单例(线程安全,初始化一次后只读)
8//! - 持有 `AppConfig` + 5 个 DB 连接配置 + Cache/Log 占位
9//! - 后续阶段:接入 SZ-ORM `Pool`,替换 `DatabaseConnection` 为真正的连接池
10//! - 接入 Cache facade
11//! - 接入日志系统
12//! - DI阶段:接入服务容器(`bind`/`singleton`/`make`,对齐 PHP `app()->bind/make/singleton`)
13//!
14//! ## PHP 对齐
15//!
16//! ```php
17//! // PHP 中的 app() 容器
18//! $app = app();
19//! $db = $app->db; // 数据库连接
20//! $cache = $app->cache; // 缓存
21//! $log = $app->log; // 日志
22//!
23//! // 服务绑定与解析(DI)
24//! app()->bind('cache', fn() => new MemoryCache());
25//! app()->singleton('db', fn() => Db::connect());
26//! $cache = app()->make('cache');
27//! ```
28//!
29//! ## Rust DI 设计
30//!
31//! Rust 中用 `TypeId` 替代 PHP 字符串 key,实现**类型安全**的服务解析:
32//!
33//! ```rust,ignore
34//! use sz_rust_core::container::App;
35//!
36//! // 注册单例(整个应用生命周期内只创建一次)
37//! App::with(|app| {
38//! app.singleton(|| MyService::new());
39//! });
40//!
41//! // 解析服务(类型安全,无需 downcast 字符串 key)
42//! let svc = App::global().unwrap().make::<MyService>();
43//! ```
44
45use crate::config::{AppConfig, DatabaseConnection};
46use parking_lot::RwLock;
47use std::any::{Any, TypeId};
48use std::collections::HashMap;
49use std::sync::{Arc, OnceLock};
50
51/// 服务实例类型别名(消除 `clippy::type_complexity` 警告)
52type ServiceInstance = Arc<dyn Any + Send + Sync>;
53/// 作用域实例缓存类型别名(消除 `clippy::type_complexity` 警告)
54type ScopeInstances = HashMap<TypeId, ServiceInstance>;
55
56/// 全局 App 容器单例
57static APP: OnceLock<App> = OnceLock::new();
58
59// ============================================================================
60// DI 服务容器(对齐 PHP app()->bind/make/singleton)
61// ============================================================================
62
63/// 服务生命周期
64///
65/// 对齐 PHP `app()->bind()`(瞬态)、`app()->singleton()`(单例)、
66/// `app()->scoped()`(请求作用域)三种语义。
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum Lifetime {
69 /// 单例:整个应用生命周期内只创建一次,后续 `make` 返回同一实例
70 Singleton,
71 /// 瞬态:每次 `make` 都调用工厂创建新实例
72 Transient,
73 /// 请求作用域:同一 `ScopeId` 内单例,不同 `ScopeId` 各自独立实例
74 ///
75 /// 对齐 PHP `app()->scoped()`。在 Rust 中,`ScopeId` 通常由 Web 框架
76 /// 在请求开始时生成(如 axum 中间件生成 UUID 的低 64 位),请求结束时
77 /// 调用 [`Container::clear_scope`] 清理。
78 Scoped,
79}
80
81/// 请求作用域 ID
82///
83/// 用于 [`Container::make_with_scope`] 区分不同请求的作用域实例。
84/// 同一 `ScopeId` 内的 `make_with_scope` 调用返回同一实例。
85pub type ScopeId = u64;
86
87/// 服务工厂函数类型
88///
89/// 返回 `Box<dyn Any + Send + Sync>` 以支持任意类型的服务实例。
90type ServiceFactory = Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync>;
91
92/// 服务绑定(工厂 + 生命周期)
93///
94/// `Clone` 用于在 `make` 中将绑定从读锁作用域复制出来后再调用工厂,
95/// 避免在持锁状态下调用用户代码(可能引发死锁或重入)。
96#[derive(Clone)]
97struct ServiceBinding {
98 /// 工厂函数(创建服务实例)
99 factory: ServiceFactory,
100 /// 生命周期策略
101 lifetime: Lifetime,
102}
103
104/// 上下文绑定工厂:无参闭包返回任意值(对齐 PHP `give()` 的工厂)
105type ContextBindingFactory = Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync>;
106
107/// 上下文绑定表:key = (消费者 TypeId, 需求 TypeId),value = 工厂
108type ContextBindingMap = HashMap<(TypeId, TypeId), ContextBindingFactory>;
109
110/// DI 服务容器 — 服务注册/解析/生命周期管理
111///
112/// 对齐 PHP `app()->bind()/make()/singleton()/instance()/scoped()/alias()`。
113/// 使用 `TypeId` 作为 key 实现类型安全的服务解析,避免 PHP 字符串 key
114/// 的类型不匹配风险。
115///
116/// ## 线程安全
117///
118/// - `bindings`、`instances`、`scoped_instances`、`aliases` 均使用 `RwLock` 保护
119/// - 单例实例以 `Arc` 返回,可跨线程共享
120pub struct Container {
121 /// 服务绑定表(TypeId → 工厂 + 生命周期)
122 bindings: RwLock<HashMap<TypeId, ServiceBinding>>,
123 /// 单例实例缓存(TypeId → 已创建实例)
124 instances: RwLock<HashMap<TypeId, ServiceInstance>>,
125 /// 请求作用域实例缓存(ScopeId → (TypeId → 实例))
126 ///
127 /// 对齐 PHP `app()->scoped()`。每个 ScopeId 相当于一个"请求作用域",
128 /// 同一作用域内首次 `make_with_scope` 调用工厂创建并缓存,后续直接返回缓存。
129 scoped_instances: RwLock<HashMap<ScopeId, ScopeInstances>>,
130 /// 字符串别名表(alias → TypeId)
131 ///
132 /// 对齐 PHP `app()->alias('name', Service::class)`。
133 /// 仅用于调试输出和 `resolve_alias` 反向查找;解析时仍用类型安全的 `make::<T>()`。
134 aliases: RwLock<HashMap<String, TypeId>>,
135 /// 标签绑定表(tag → `Vec<TypeId\>`)
136 ///
137 /// 对齐 PHP `app()->tag(['Logger', 'Mailer'], 'reporters')`。
138 /// 通过 `tagged::<T>()` 获取标签下所有类型匹配的实例。
139 tags: RwLock<HashMap<String, Vec<TypeId>>>,
140 /// 上下文绑定表((消费者 TypeId, 需求 TypeId) → 工厂)
141 ///
142 /// 对齐 PHP `app()->when(PhotoController::class)->needs(Filesystem::class)->give(S3Filesystem::class)`。
143 /// 通过 `make_for::<T, Consumer>()` 为指定消费者解析上下文绑定的服务。
144 context_bindings: RwLock<ContextBindingMap>,
145 /// 循环依赖检测栈:记录当前正在构造中的服务类型链
146 ///
147 /// 用于检测 A → B → C → A 形式的循环依赖。
148 /// 工厂调用期间若发现目标类型已在栈中,立即 panic 并输出完整依赖链。
149 ///
150 /// 存储 `(&'static str, TypeId)` 对:TypeId 用于 O(1) 查找,
151 /// 类型名用于生成可读的错误信息(如 "ServiceA -> ServiceB -> ServiceA")。
152 constructing: RwLock<Vec<(&'static str, TypeId)>>,
153}
154
155impl Container {
156 /// 创建空的服务容器
157 pub fn new() -> Self {
158 Self {
159 bindings: RwLock::new(HashMap::new()),
160 instances: RwLock::new(HashMap::new()),
161 scoped_instances: RwLock::new(HashMap::new()),
162 aliases: RwLock::new(HashMap::new()),
163 tags: RwLock::new(HashMap::new()),
164 context_bindings: RwLock::new(HashMap::new()),
165 constructing: RwLock::new(Vec::new()),
166 }
167 }
168
169 /// 注册瞬态服务(每次 `make` 创建新实例)
170 ///
171 /// 对齐 PHP `app()->bind('key', fn() => new Service())`。
172 ///
173 /// # 类型约束
174 ///
175 /// - `T: Send + Sync + 'static`:服务实例必须线程安全
176 /// - `F: Fn() -> T + Send + Sync + 'static`:工厂必须线程安全
177 pub fn bind<T, F>(&self, factory: F)
178 where
179 T: Send + Sync + 'static,
180 F: Fn() -> T + Send + Sync + 'static,
181 {
182 let type_id = TypeId::of::<T>();
183 let binding = ServiceBinding {
184 factory: Arc::new(move || Box::new(factory())),
185 lifetime: Lifetime::Transient,
186 };
187 self.bindings.write().insert(type_id, binding);
188 }
189
190 /// 注册单例服务(整个应用生命周期内只创建一次)
191 ///
192 /// 对齐 PHP `app()->singleton('key', fn() => new Service())`。
193 ///
194 /// 首次 `make` 时调用工厂创建实例并缓存,后续 `make` 返回缓存的同一实例。
195 pub fn singleton<T, F>(&self, factory: F)
196 where
197 T: Send + Sync + 'static,
198 F: Fn() -> T + Send + Sync + 'static,
199 {
200 let type_id = TypeId::of::<T>();
201 let binding = ServiceBinding {
202 factory: Arc::new(move || Box::new(factory())),
203 lifetime: Lifetime::Singleton,
204 };
205 self.bindings.write().insert(type_id, binding);
206 }
207
208 /// 注册请求作用域服务(同一 `ScopeId` 内单例)
209 ///
210 /// 对齐 PHP `app()->scoped('key', fn() => new Service())`。
211 ///
212 /// 与 `singleton` 不同:scoped 服务在 [`Container::make_with_scope`] 调用时,
213 /// 同一 `scope_id` 内首次调用工厂创建并缓存,后续返回缓存;
214 /// 不同 `scope_id` 各自创建独立实例;请求结束后调用
215 /// [`Container::clear_scope`] 清理对应作用域的缓存。
216 ///
217 /// # 用法
218 ///
219 /// ```ignore
220 /// use sz_rust_core::container::{Container, ScopeId};
221 ///
222 /// let container = Container::new();
223 /// container.scoped(|| RequestCache::new());
224 ///
225 /// // 请求 A(scope_id=1)
226 /// let cache_a1 = container.make_with_scope::<RequestCache>(1).unwrap();
227 /// let cache_a2 = container.make_with_scope::<RequestCache>(1).unwrap();
228 /// assert!(Arc::ptr_eq(&cache_a1, &cache_a2)); // 同一作用域:同一实例
229 ///
230 /// // 请求 B(scope_id=2)
231 /// let cache_b = container.make_with_scope::<RequestCache>(2).unwrap();
232 /// assert!(!Arc::ptr_eq(&cache_a1, &cache_b)); // 不同作用域:不同实例
233 ///
234 /// // 请求 A 结束
235 /// container.clear_scope(1);
236 /// ```
237 pub fn scoped<T, F>(&self, factory: F)
238 where
239 T: Send + Sync + 'static,
240 F: Fn() -> T + Send + Sync + 'static,
241 {
242 let type_id = TypeId::of::<T>();
243 let binding = ServiceBinding {
244 factory: Arc::new(move || Box::new(factory())),
245 lifetime: Lifetime::Scoped,
246 };
247 self.bindings.write().insert(type_id, binding);
248 }
249
250 /// 直接绑定已创建的实例(绕过工厂)
251 ///
252 /// 对齐 PHP `app()->instance('key', $obj)`。
253 ///
254 /// 将一个已创建的实例直接注册为单例,后续 `make` 返回此实例。
255 /// 适用于:
256 /// - 实例已在其他地方创建(如配置加载时初始化的服务)
257 /// - 实例创建过程复杂、不适合用闭包表达
258 /// - 测试中注入 mock 实例
259 ///
260 /// # 用法
261 ///
262 /// ```ignore
263 /// use sz_rust_core::container::Container;
264 ///
265 /// let container = Container::new();
266 /// let logger = Arc::new(FileLogger::new("/var/log/app.log"));
267 /// container.instance(logger.clone());
268 ///
269 /// let resolved = container.make::<FileLogger>().unwrap();
270 /// assert!(Arc::ptr_eq(&logger, &resolved));
271 /// ```
272 pub fn instance<T>(&self, instance: T)
273 where
274 T: Send + Sync + 'static,
275 {
276 let type_id = TypeId::of::<T>();
277 let arc: Arc<dyn Any + Send + Sync> = Arc::new(instance);
278 // 1. 缓存实例(make 会优先检查 instances 缓存)
279 self.instances.write().insert(type_id, arc);
280 // 2. 注册占位绑定(使 has() 返回 true)
281 // 注:factory 不会被调用,因为 make 会先命中 instances 缓存。
282 // 使用 unreachable 闭包表达此不变量;若被调用则说明内部状态被破坏。
283 self.bindings.write().insert(
284 type_id,
285 ServiceBinding {
286 factory: Arc::new(|| {
287 panic!("instance() 绑定的服务不应调用工厂 — 这是内部不变量违反")
288 }),
289 lifetime: Lifetime::Singleton,
290 },
291 );
292 }
293
294 /// 为服务类型注册字符串别名
295 ///
296 /// 对齐 PHP `app()->alias('name', Service::class)`。
297 ///
298 /// 别名仅用于:
299 /// - 调试输出([`Container::debug_aliases`] 列出所有别名)
300 /// - 反向查找([`Container::resolve_alias`] 通过别名获取 TypeId)
301 ///
302 /// 解析时仍用类型安全的 `make::<T>()`,不支持通过字符串别名解析
303 /// (Rust 类型系统要求编译时已知类型,字符串 key 解析会引入不安全的 downcast)。
304 ///
305 /// # 用法
306 ///
307 /// ```ignore
308 /// use sz_rust_core::container::Container;
309 ///
310 /// let container = Container::new();
311 /// container.singleton(|| MyService::new());
312 /// container.alias::<MyService>("my_service");
313 ///
314 /// assert!(container.is_alias("my_service"));
315 /// let type_id = container.resolve_alias("my_service").unwrap();
316 /// assert_eq!(type_id, std::any::TypeId::of::<MyService>());
317 /// ```
318 pub fn alias<T: 'static>(&self, name: impl Into<String>) {
319 let type_id = TypeId::of::<T>();
320 self.aliases.write().insert(name.into(), type_id);
321 }
322
323 /// 通过别名查找对应的 TypeId
324 ///
325 /// 返回 `None` 表示别名未注册。
326 pub fn resolve_alias(&self, name: &str) -> Option<TypeId> {
327 self.aliases.read().get(name).copied()
328 }
329
330 /// 检查指定别名是否已注册
331 pub fn is_alias(&self, name: &str) -> bool {
332 self.aliases.read().contains_key(name)
333 }
334
335 /// 列出所有已注册别名(用于调试)
336 pub fn debug_aliases(&self) -> Vec<String> {
337 self.aliases.read().keys().cloned().collect()
338 }
339
340 /// 解析服务实例(无作用域)
341 ///
342 /// 对齐 PHP `app()->make('key')`。
343 ///
344 /// 等价于 [`Container::make_with_scope`] 传入 `scope_id = 0`。
345 /// 对于 `Scoped` 生命周期服务,会使用 `scope_id = 0` 作为默认作用域。
346 ///
347 /// # 返回
348 ///
349 /// - `Some(Arc<T>)`:服务已注册,返回实例(单例返回缓存实例,瞬态返回新实例)
350 /// - `None`:服务未注册
351 ///
352 /// # Panics
353 ///
354 /// 理论上不会 panic(工厂返回的 `Box<dyn Any>` 内部类型由编译时泛型保证)。
355 /// 若发生 panic 说明内部状态被破坏(bindings 与 instances 不一致)。
356 pub fn make<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
357 self.make_with_scope::<T>(0)
358 }
359
360 /// 解析服务实例(带作用域 ID)
361 ///
362 /// 对齐 PHP `app()->make('key')` + 请求作用域支持。
363 ///
364 /// # 生命周期处理
365 ///
366 /// - `Singleton`:忽略 `scope_id`,返回全局缓存的单例
367 /// - `Transient`:忽略 `scope_id`,每次调用工厂创建新实例
368 /// - `Scoped`:同一 `scope_id` 内首次调用工厂创建并缓存,后续返回缓存
369 ///
370 /// # 返回
371 ///
372 /// - `Some(Arc<T>)`:服务已注册,返回实例
373 /// - `None`:服务未注册
374 ///
375 /// # Panics
376 ///
377 /// 理论上不会 panic(工厂返回的 `Box<dyn Any>` 内部类型由编译时泛型保证)。
378 /// 若发生 panic 说明内部状态被破坏。
379 pub fn make_with_scope<T: Send + Sync + 'static>(&self, scope_id: ScopeId) -> Option<Arc<T>> {
380 let type_id = TypeId::of::<T>();
381
382 // 1. 检查全局单例缓存(singleton 和 instance 都会写入此缓存)
383 if let Some(cached) = self.instances.read().get(&type_id) {
384 return Arc::downcast::<T>(cached.clone()).ok();
385 }
386
387 // 2. 检查作用域缓存(仅 Scoped 生命周期)
388 if scope_id != 0 {
389 let scoped = self.scoped_instances.read();
390 if let Some(scope_map) = scoped.get(&scope_id) {
391 if let Some(cached) = scope_map.get(&type_id) {
392 return Arc::downcast::<T>(cached.clone()).ok();
393 }
394 }
395 }
396
397 // 3. 查找绑定
398 // 注:先绑定 `let` 延长 `RwLockReadGuard` 生命周期,避免临时值被释放
399 let guard = self.bindings.read();
400 let binding = guard.get(&type_id)?.clone();
401 drop(guard); // 释放读锁后再调用工厂(避免持锁调用用户代码引发死锁/重入)
402
403 // 4. 循环依赖检测 + 工厂调用(共用逻辑,含 make_for)
404 let type_name = std::any::type_name::<T>();
405 let instance = self.check_and_call_factory(type_id, type_name, &binding.factory)?;
406
407 match binding.lifetime {
408 Lifetime::Singleton => {
409 let arc: Arc<dyn Any + Send + Sync> = Arc::from(instance);
410 self.instances.write().insert(type_id, arc.clone());
411 Arc::downcast::<T>(arc).ok()
412 }
413 Lifetime::Scoped => {
414 let arc: Arc<dyn Any + Send + Sync> = Arc::from(instance);
415 self.scoped_instances
416 .write()
417 .entry(scope_id)
418 .or_default()
419 .insert(type_id, arc.clone());
420 Arc::downcast::<T>(arc).ok()
421 }
422 Lifetime::Transient => {
423 // 瞬态:直接返回(不缓存)
424 Arc::downcast::<T>(Arc::from(instance)).ok()
425 }
426 }
427 }
428
429 /// 清理指定作用域的所有缓存实例
430 ///
431 /// 应在请求结束时调用(如 axum 中间件在请求处理完毕后调用),
432 /// 释放该作用域内创建的所有 Scoped 服务实例。
433 ///
434 /// # 用法
435 ///
436 /// ```ignore
437 /// use sz_rust_core::container::Container;
438 ///
439 /// let container = Container::new();
440 /// container.scoped(|| RequestCache::new());
441 ///
442 /// let scope_id = generate_scope_id(); // 如从 axum State 获取
443 /// let _cache = container.make_with_scope::<RequestCache>(scope_id);
444 ///
445 /// // 请求结束
446 /// container.clear_scope(scope_id);
447 /// ```
448 pub fn clear_scope(&self, scope_id: ScopeId) {
449 self.scoped_instances.write().remove(&scope_id);
450 }
451
452 /// 检查服务是否已注册
453 pub fn has<T: 'static>(&self) -> bool {
454 let type_id = TypeId::of::<T>();
455 self.bindings.read().contains_key(&type_id)
456 }
457
458 /// 移除指定类型的服务绑定(含单例缓存与所有作用域缓存)
459 ///
460 /// 对齐 PHP `app()->remove('key')`。
461 pub fn forget<T: 'static>(&self) {
462 let type_id = TypeId::of::<T>();
463 self.bindings.write().remove(&type_id);
464 self.instances.write().remove(&type_id);
465 // 清理所有作用域中该类型的缓存
466 let mut scoped = self.scoped_instances.write();
467 for scope_map in scoped.values_mut() {
468 scope_map.remove(&type_id);
469 }
470 }
471
472 /// 清空所有服务绑定与缓存(含单例、作用域、别名、标签、上下文绑定)
473 pub fn clear(&self) {
474 self.bindings.write().clear();
475 self.instances.write().clear();
476 self.scoped_instances.write().clear();
477 self.aliases.write().clear();
478 self.tags.write().clear();
479 self.context_bindings.write().clear();
480 self.constructing.write().clear();
481 }
482
483 /// 当前构造栈深度(用于调试,正常应为 0)
484 pub fn constructing_depth(&self) -> usize {
485 self.constructing.read().len()
486 }
487
488 /// 已注册服务数量(不含别名)
489 pub fn count(&self) -> usize {
490 self.bindings.read().len()
491 }
492
493 /// 已注册别名数量
494 pub fn alias_count(&self) -> usize {
495 self.aliases.read().len()
496 }
497
498 /// 当前活跃作用域数量
499 ///
500 /// 可用于检测作用域泄漏(如请求结束未调用 `clear_scope`)。
501 pub fn active_scope_count(&self) -> usize {
502 self.scoped_instances.read().len()
503 }
504
505 // ========================================================================
506 // 标签绑定(对齐 PHP `app()->tag()` / `app()->tagged()`)
507 // ========================================================================
508
509 /// 给类型 T 打标签(对齐 PHP `app()->tag(['Service'], 'tag_name')`)
510 ///
511 /// PHP 用法:
512 /// ```php
513 /// $this->app->tag(['Logger', 'Mailer', 'Notifier'], 'reporters');
514 /// ```
515 ///
516 /// Rust 端由于类型安全,每次调用只能给一个类型打标签。
517 /// 多次调用同一标签名会追加到标签列表。
518 ///
519 /// # 用法
520 ///
521 /// ```ignore
522 /// use sz_rust_core::container::Container;
523 ///
524 /// let container = Container::new();
525 /// container.singleton(|| FileLogger::new());
526 /// container.singleton(|| MailLogger::new());
527 ///
528 /// container.tag::<FileLogger>("reporters");
529 /// container.tag::<MailLogger>("reporters");
530 ///
531 /// let reporters = container.tagged::<FileLogger>("reporters");
532 /// assert_eq!(reporters.len(), 1);
533 /// ```
534 pub fn tag<T: 'static>(&self, tag: impl Into<String>) {
535 let type_id = TypeId::of::<T>();
536 let tag_name = tag.into();
537 self.tags.write().entry(tag_name).or_default().push(type_id);
538 }
539
540 /// 获取标签下所有 T 类型实例(对齐 PHP `app()->tagged('tag_name')`)
541 ///
542 /// 遍历标签下所有 TypeId,对每个匹配 `T` 的 TypeId 调用 `make::<T>()`。
543 ///
544 /// # 返回
545 ///
546 /// 标签下所有类型为 `T` 的服务实例向量。若标签不存在或无匹配类型,返回空向量。
547 pub fn tagged<T: Send + Sync + 'static>(&self, tag: &str) -> Vec<Arc<T>> {
548 let type_ids = match self.tags.read().get(tag) {
549 Some(ids) => ids.clone(),
550 None => return Vec::new(),
551 };
552
553 let target_type_id = TypeId::of::<T>();
554 type_ids
555 .into_iter()
556 .filter(|id| *id == target_type_id)
557 .filter_map(|_| self.make::<T>())
558 .collect()
559 }
560
561 /// 获取标签下所有 TypeId(用于调试)
562 ///
563 /// 返回标签下所有已注册的 TypeId 列表。若标签不存在,返回空向量。
564 pub fn tagged_type_ids(&self, tag: &str) -> Vec<TypeId> {
565 self.tags.read().get(tag).cloned().unwrap_or_default()
566 }
567
568 /// 获取已注册标签列表
569 pub fn tag_names(&self) -> Vec<String> {
570 self.tags.read().keys().cloned().collect()
571 }
572
573 /// 获取标签下已注册的类型数量
574 pub fn tag_count(&self, tag: &str) -> usize {
575 self.tags.read().get(tag).map(|ids| ids.len()).unwrap_or(0)
576 }
577
578 /// 移除指定标签(对齐 PHP `app()->forgetTag('tag_name')`)
579 pub fn forget_tag(&self, tag: &str) {
580 self.tags.write().remove(tag);
581 }
582
583 // ========================================================================
584 // 上下文绑定(对齐 PHP `app()->when()->needs()->give()`)
585 // ========================================================================
586
587 /// 注册上下文绑定(对齐 PHP `app()->when(Consumer)->needs(Need)->give(impl)`)
588 ///
589 /// PHP 用法:
590 /// ```php
591 /// $this->app->when(PhotoController::class)
592 /// ->needs(Filesystem::class)
593 /// ->give(function () { return new S3Filesystem(); });
594 /// ```
595 ///
596 /// Rust 端通过泛型参数指定消费者类型 `Consumer`、需求类型 `T`,
597 /// 并提供工厂闭包创建 `T` 实例。
598 ///
599 /// # 用法
600 ///
601 /// ```ignore
602 /// use sz_rust_core::container::Container;
603 ///
604 /// let container = Container::new();
605 ///
606 /// // 为 PhotoController 注入 S3Filesystem 作为 Filesystem
607 /// container.bind_contextual::<PhotoController, Filesystem, _>(|| {
608 /// S3Filesystem::new()
609 /// });
610 ///
611 /// // 解析:为 PhotoController 创建 Filesystem 实例
612 /// let fs = container.make_for::<Filesystem, PhotoController>();
613 /// ```
614 ///
615 /// # 注意
616 ///
617 /// 上下文绑定不会缓存实例(每次 `make_for` 调用工厂)。
618 pub fn bind_contextual<Consumer: 'static, T: Send + Sync + 'static, F>(&self, factory: F)
619 where
620 F: Fn() -> T + Send + Sync + 'static,
621 {
622 let key = (TypeId::of::<Consumer>(), TypeId::of::<T>());
623 let arc_factory: Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync> =
624 Arc::new(move || Box::new(factory()));
625 self.context_bindings.write().insert(key, arc_factory);
626 }
627
628 /// 为指定消费者解析上下文绑定的服务(对齐 PHP 上下文感知 `make`)
629 ///
630 /// 查找 `(Consumer, T)` 的上下文绑定,若存在则调用工厂返回实例。
631 /// 若不存在上下文绑定,回退到普通 `make::<T>()`。
632 ///
633 /// # 返回
634 ///
635 /// - `Some(Arc<T>)`:找到上下文绑定或普通绑定
636 /// - `None`:既无上下文绑定也无普通绑定
637 pub fn make_for<T: Send + Sync + 'static, Consumer: 'static>(&self) -> Option<Arc<T>> {
638 let key = (TypeId::of::<Consumer>(), TypeId::of::<T>());
639
640 // 1. 检查上下文绑定
641 let factory = {
642 let guard = self.context_bindings.read();
643 guard.get(&key).cloned()
644 };
645
646 if let Some(factory) = factory {
647 // 上下文绑定同样需要循环依赖检测
648 let type_id = TypeId::of::<T>();
649 let type_name = std::any::type_name::<T>();
650 let instance = self.check_and_call_factory(type_id, type_name, &factory);
651 instance.and_then(|inst| Arc::downcast::<T>(Arc::from(inst)).ok())
652 } else {
653 // 2. 回退到普通 make
654 self.make::<T>()
655 }
656 }
657
658 /// 循环依赖检测 + 工厂调用(供 make_with_scope 和 make_for 共用)
659 ///
660 /// 检查目标类型是否已在构造栈中,若是则 panic;
661 /// 否则压栈、调用工厂、弹栈,返回原始工厂输出(`Box<dyn Any + Send + Sync>`)。
662 /// 调用方负责将 `Box` 转为 `Arc` 并按生命周期策略缓存。
663 fn check_and_call_factory(
664 &self,
665 type_id: TypeId,
666 type_name: &'static str,
667 factory: &ServiceFactory,
668 ) -> Option<Box<dyn Any + Send + Sync>> {
669 // 循环依赖检测
670 {
671 let constructing = self.constructing.read();
672 if constructing.iter().any(|(_, tid)| *tid == type_id) {
673 let chain: Vec<&str> = constructing
674 .iter()
675 .skip_while(|(_, tid)| *tid != type_id)
676 .map(|(name, _)| *name)
677 .chain(std::iter::once(type_name))
678 .collect();
679 drop(constructing);
680 panic!(
681 "DI 容器检测到循环依赖: {}",
682 chain.join(" -> ")
683 );
684 }
685 }
686
687 self.constructing.write().push((type_name, type_id));
688 let instance = factory();
689 self.constructing.write().pop();
690
691 Some(instance)
692 }
693
694 /// 检查指定上下文绑定是否存在
695 pub fn has_contextual<Consumer: 'static, T: 'static>(&self) -> bool {
696 let key = (TypeId::of::<Consumer>(), TypeId::of::<T>());
697 self.context_bindings.read().contains_key(&key)
698 }
699
700 /// 获取上下文绑定数量
701 pub fn contextual_count(&self) -> usize {
702 self.context_bindings.read().len()
703 }
704
705 /// 移除指定上下文绑定
706 pub fn forget_contextual<Consumer: 'static, T: 'static>(&self) {
707 let key = (TypeId::of::<Consumer>(), TypeId::of::<T>());
708 self.context_bindings.write().remove(&key);
709 }
710
711 // ========================================================================
712 // 方法调用 / 自动注入(对齐 PHP `app()->call()` / `app()->invoke()`)
713 // ========================================================================
714
715 /// 调用闭包并自动注入参数 — 对齐 PHP `app()->call($callback, $parameters)`
716 ///
717 /// PHP 的 `app()->call()` 通过反射自动解析方法参数类型并从容器获取实例。
718 /// Rust 是静态类型语言,无法运行时反射,因此通过 `resolver` 闭包手工指定
719 /// 如何从 Container 解析参数。
720 ///
721 /// # 参数
722 ///
723 /// - `resolver`: 参数解析器,接收 `&Container` 引用,返回参数元组
724 /// - `callback`: 业务回调,接收解析后的参数,返回业务结果
725 ///
726 /// # 返回
727 ///
728 /// 业务回调的返回值
729 ///
730 /// # 用法
731 ///
732 /// ```ignore
733 /// use sz_rust_core::container::Container;
734 ///
735 /// struct UserService;
736 /// struct Logger;
737 ///
738 /// let container = Container::new();
739 /// container.singleton(Logger::new);
740 /// container.singleton(UserService::new);
741 ///
742 /// // 自动注入 Logger 和 UserService
743 /// let result: String = container.call_method(
744 /// |c| (c.make::<Logger>().unwrap(), c.make::<UserService>().unwrap()),
745 /// |(logger, service)| {
746 /// format!("called with logger and service")
747 /// },
748 /// );
749 /// ```
750 pub fn call_method<R, P, F, C>(&self, resolver: C, callback: F) -> R
751 where
752 F: FnOnce(P) -> R,
753 C: FnOnce(&Self) -> P,
754 {
755 let params = resolver(self);
756 callback(params)
757 }
758
759 /// 调用闭包并传入容器引用 — 对齐 PHP `app()->invoke($callback)`
760 ///
761 /// 最灵活的方法调用方式,调用方可以在闭包内自由调用 `make()` 解析依赖。
762 ///
763 /// # 用法
764 ///
765 /// ```ignore
766 /// let result: String = container.invoke(|c| {
767 /// let logger = c.make::<Logger>().unwrap();
768 /// let service = c.make::<UserService>().unwrap();
769 /// format!("called with {:?} and {:?}", logger, service)
770 /// });
771 /// ```
772 pub fn invoke<R, F>(&self, callback: F) -> R
773 where
774 F: FnOnce(&Self) -> R,
775 {
776 callback(self)
777 }
778
779 /// 解析服务,失败时 panic — 用于自动注入场景
780 ///
781 /// 对齐 PHP `app()->make()` 在服务未注册时抛出异常的行为。
782 /// Rust 端通过 panic 模拟,调用方应在确保服务已注册时使用。
783 ///
784 /// # Panics
785 ///
786 /// 当服务未注册时 panic。
787 pub fn make_or_panic<T: Send + Sync + 'static>(&self) -> Arc<T> {
788 match self.make::<T>() {
789 Some(instance) => instance,
790 None => panic!(
791 "无法解析服务: {} — 请确保已通过 bind/singleton/scoped 注册",
792 std::any::type_name::<T>()
793 ),
794 }
795 }
796}
797
798impl Default for Container {
799 fn default() -> Self {
800 Self::new()
801 }
802}
803
804impl std::fmt::Debug for Container {
805 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
806 f.debug_struct("Container")
807 .field("bindings_count", &self.bindings.read().len())
808 .field("instances_count", &self.instances.read().len())
809 .field("scoped_scope_count", &self.scoped_instances.read().len())
810 .field("aliases_count", &self.aliases.read().len())
811 .finish()
812 }
813}
814
815// ============================================================================
816// App 容器(全局单例)
817// ============================================================================
818
819/// App 容器(全局单例)
820///
821/// 持有应用配置、各子系统单例和 DI 服务容器。通过 [`App::global()`] 获取全局实例,
822/// 通过 [`App::init()`] 初始化。
823pub struct App {
824 /// 应用配置(只读,初始化后不可变)
825 config: AppConfig,
826 /// 数据库连接配置(5 个:mysql/njszjt/ljclz/food/oceanbase)
827 /// 后续将替换为 SZ-ORM `Pool` 实例
828 db_connections: HashMap<String, DatabaseConnection>,
829 /// Cache 单例占位(接入真正的 Cache facade)
830 cache: RwLock<Option<String>>,
831 /// Log 单例占位(接入 sz-orm-logger + tracing)
832 log: RwLock<Option<String>>,
833 /// DI 服务容器(服务注册/解析/生命周期管理)
834 container: Container,
835}
836
837impl App {
838 /// 构造 App 实例(不注册到全局单例)
839 ///
840 /// 用于测试或显式持有实例的场景。生产代码应使用 [`App::init()`] 注册全局单例。
841 pub fn new(config: AppConfig) -> App {
842 let db_connections = config.database.connections.clone();
843 App {
844 config,
845 db_connections,
846 cache: RwLock::new(None),
847 log: RwLock::new(None),
848 container: Container::new(),
849 }
850 }
851
852 /// 初始化全局 App 容器
853 ///
854 /// 只能调用一次,重复调用返回已有实例。
855 ///
856 /// ```rust,ignore
857 /// use sz_rust_core::container::App;
858 /// use sz_rust_core::config::AppConfig;
859 ///
860 /// let config = AppConfig::load_from_dir("config").unwrap();
861 /// let app = App::init(config);
862 /// ```
863 pub fn init(config: AppConfig) -> &'static App {
864 APP.get_or_init(|| App::new(config))
865 }
866
867 /// 获取全局 App 容器实例
868 ///
869 /// 必须先调用 [`App::init()`] 初始化,否则返回 `None`。
870 ///
871 /// # 命名说明
872 ///
873 /// 此方法对应 PHP `app()` helper(获取全局容器实例)。
874 /// 不使用 `App::instance()` 是为了避免与 [`App::instance<T>`](绑定实例方法,
875 /// 对齐 PHP `app()->instance('key', $obj)`)冲突。
876 pub fn global() -> Option<&'static App> {
877 APP.get()
878 }
879
880 /// 获取应用配置
881 pub fn config(&self) -> &AppConfig {
882 &self.config
883 }
884
885 /// 获取数据库连接配置
886 ///
887 /// 对齐 PHP `Db::connect('mysql')`。
888 ///
889 /// 当前返回 `DatabaseConnection` 配置。
890 /// 后续将替换为 SZ-ORM `Pool` 实例。
891 pub fn db_connection(&self, name: &str) -> Option<&DatabaseConnection> {
892 self.db_connections.get(name)
893 }
894
895 /// 获取所有数据库连接名称
896 pub fn db_connection_names(&self) -> Vec<&str> {
897 self.db_connections.keys().map(|s| s.as_str()).collect()
898 }
899
900 /// 获取默认数据库连接配置
901 pub fn default_db_connection(&self) -> Option<&DatabaseConnection> {
902 self.db_connection(&self.config.database.default)
903 }
904
905 /// 设置 Cache 单例(将替换为真正的 Cache facade)
906 pub fn set_cache(&self, cache: impl Into<String>) {
907 let mut guard = self.cache.write();
908 *guard = Some(cache.into());
909 }
910
911 /// 获取 Cache 单例
912 pub fn cache(&self) -> Option<String> {
913 self.cache.read().clone()
914 }
915
916 /// 设置 Log 单例(将替换为真正的日志系统)
917 pub fn set_log(&self, log: impl Into<String>) {
918 let mut guard = self.log.write();
919 *guard = Some(log.into());
920 }
921
922 /// 获取 Log 单例
923 pub fn log(&self) -> Option<String> {
924 self.log.read().clone()
925 }
926
927 // ========================================================================
928 // DI 服务容器代理方法(对齐 PHP app()->bind/make/singleton/scoped/instance/alias)
929 // ========================================================================
930
931 /// 获取 DI 服务容器引用
932 pub fn container(&self) -> &Container {
933 &self.container
934 }
935
936 /// 注册瞬态服务
937 ///
938 /// 对齐 PHP `app()->bind('key', fn() => new Service())`。
939 pub fn bind<T, F>(&self, factory: F)
940 where
941 T: Send + Sync + 'static,
942 F: Fn() -> T + Send + Sync + 'static,
943 {
944 self.container.bind(factory);
945 }
946
947 /// 注册单例服务
948 ///
949 /// 对齐 PHP `app()->singleton('key', fn() => new Service())`。
950 pub fn singleton<T, F>(&self, factory: F)
951 where
952 T: Send + Sync + 'static,
953 F: Fn() -> T + Send + Sync + 'static,
954 {
955 self.container.singleton(factory);
956 }
957
958 /// 注册请求作用域服务
959 ///
960 /// 对齐 PHP `app()->scoped('key', fn() => new Service())`。
961 pub fn scoped<T, F>(&self, factory: F)
962 where
963 T: Send + Sync + 'static,
964 F: Fn() -> T + Send + Sync + 'static,
965 {
966 self.container.scoped(factory);
967 }
968
969 /// 直接绑定已创建的实例
970 ///
971 /// 对齐 PHP `app()->instance('key', $obj)`。
972 pub fn instance<T>(&self, instance: T)
973 where
974 T: Send + Sync + 'static,
975 {
976 self.container.instance(instance);
977 }
978
979 /// 为服务类型注册字符串别名
980 ///
981 /// 对齐 PHP `app()->alias('name', Service::class)`。
982 pub fn alias<T: 'static>(&self, name: impl Into<String>) {
983 self.container.alias::<T>(name);
984 }
985
986 /// 解析服务实例(自动感知请求作用域)
987 ///
988 /// 对齐 PHP `app()->make('key')`。
989 ///
990 /// P1-ARCH-DI-02:若当前线程处于 [`RequestScopeLayer`](crate::middleware::request_scope::RequestScopeLayer)
991 /// 管理的请求中(即 `current_scope_id()` 返回 `Some`),则自动路由到
992 /// `make_with_scope`,使 Scoped 绑定在请求内缓存、请求结束清理。
993 /// 请求外调用则退化为无作用域的 `make`(向后兼容)。
994 pub fn make<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
995 if let Some(scope_id) = crate::middleware::request_scope::current_scope_id() {
996 self.container.make_with_scope::<T>(scope_id)
997 } else {
998 self.container.make::<T>()
999 }
1000 }
1001
1002 /// 解析服务实例(带作用域 ID)
1003 ///
1004 /// 对齐 PHP `app()->make('key')` + 请求作用域支持。
1005 pub fn make_with_scope<T: Send + Sync + 'static>(&self, scope_id: ScopeId) -> Option<Arc<T>> {
1006 self.container.make_with_scope::<T>(scope_id)
1007 }
1008
1009 /// 清理指定作用域的所有缓存实例
1010 pub fn clear_scope(&self, scope_id: ScopeId) {
1011 self.container.clear_scope(scope_id);
1012 }
1013
1014 /// 检查服务是否已注册
1015 pub fn has_service<T: 'static>(&self) -> bool {
1016 self.container.has::<T>()
1017 }
1018}
1019
1020impl std::fmt::Debug for App {
1021 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1022 f.debug_struct("App")
1023 .field("config", &self.config)
1024 .field(
1025 "db_connections",
1026 &self.db_connections.keys().collect::<Vec<_>>(),
1027 )
1028 .field("cache", &self.cache.read().is_some())
1029 .field("log", &self.log.read().is_some())
1030 .field("container", &self.container)
1031 .finish()
1032 }
1033}
1034
1035// ============================================================================
1036// 单元测试(分离到 tests.rs,降低单文件认知负担)
1037// ============================================================================
1038
1039#[cfg(test)]
1040mod tests;