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/// DI 服务容器 — 服务注册/解析/生命周期管理
105///
106/// 对齐 PHP `app()->bind()/make()/singleton()/instance()/scoped()/alias()`。
107/// 使用 `TypeId` 作为 key 实现类型安全的服务解析,避免 PHP 字符串 key
108/// 的类型不匹配风险。
109///
110/// ## 线程安全
111///
112/// - `bindings`、`instances`、`scoped_instances`、`aliases` 均使用 `RwLock` 保护
113/// - 单例实例以 `Arc` 返回,可跨线程共享
114pub struct Container {
115 /// 服务绑定表(TypeId → 工厂 + 生命周期)
116 bindings: RwLock<HashMap<TypeId, ServiceBinding>>,
117 /// 单例实例缓存(TypeId → 已创建实例)
118 instances: RwLock<HashMap<TypeId, ServiceInstance>>,
119 /// 请求作用域实例缓存(ScopeId → (TypeId → 实例))
120 ///
121 /// 对齐 PHP `app()->scoped()`。每个 ScopeId 相当于一个"请求作用域",
122 /// 同一作用域内首次 `make_with_scope` 调用工厂创建并缓存,后续直接返回缓存。
123 scoped_instances: RwLock<HashMap<ScopeId, ScopeInstances>>,
124 /// 字符串别名表(alias → TypeId)
125 ///
126 /// 对齐 PHP `app()->alias('name', Service::class)`。
127 /// 仅用于调试输出和 `resolve_alias` 反向查找;解析时仍用类型安全的 `make::<T>()`。
128 aliases: RwLock<HashMap<String, TypeId>>,
129}
130
131impl Container {
132 /// 创建空的服务容器
133 pub fn new() -> Self {
134 Self {
135 bindings: RwLock::new(HashMap::new()),
136 instances: RwLock::new(HashMap::new()),
137 scoped_instances: RwLock::new(HashMap::new()),
138 aliases: RwLock::new(HashMap::new()),
139 }
140 }
141
142 /// 注册瞬态服务(每次 `make` 创建新实例)
143 ///
144 /// 对齐 PHP `app()->bind('key', fn() => new Service())`。
145 ///
146 /// # 类型约束
147 ///
148 /// - `T: Send + Sync + 'static`:服务实例必须线程安全
149 /// - `F: Fn() -> T + Send + Sync + 'static`:工厂必须线程安全
150 pub fn bind<T, F>(&self, factory: F)
151 where
152 T: Send + Sync + 'static,
153 F: Fn() -> T + Send + Sync + 'static,
154 {
155 let type_id = TypeId::of::<T>();
156 let binding = ServiceBinding {
157 factory: Arc::new(move || Box::new(factory())),
158 lifetime: Lifetime::Transient,
159 };
160 self.bindings.write().insert(type_id, binding);
161 }
162
163 /// 注册单例服务(整个应用生命周期内只创建一次)
164 ///
165 /// 对齐 PHP `app()->singleton('key', fn() => new Service())`。
166 ///
167 /// 首次 `make` 时调用工厂创建实例并缓存,后续 `make` 返回缓存的同一实例。
168 pub fn singleton<T, F>(&self, factory: F)
169 where
170 T: Send + Sync + 'static,
171 F: Fn() -> T + Send + Sync + 'static,
172 {
173 let type_id = TypeId::of::<T>();
174 let binding = ServiceBinding {
175 factory: Arc::new(move || Box::new(factory())),
176 lifetime: Lifetime::Singleton,
177 };
178 self.bindings.write().insert(type_id, binding);
179 }
180
181 /// 注册请求作用域服务(同一 `ScopeId` 内单例)
182 ///
183 /// 对齐 PHP `app()->scoped('key', fn() => new Service())`。
184 ///
185 /// 与 `singleton` 不同:scoped 服务在 [`Container::make_with_scope`] 调用时,
186 /// 同一 `scope_id` 内首次调用工厂创建并缓存,后续返回缓存;
187 /// 不同 `scope_id` 各自创建独立实例;请求结束后调用
188 /// [`Container::clear_scope`] 清理对应作用域的缓存。
189 ///
190 /// # 用法
191 ///
192 /// ```ignore
193 /// use sz_rust_core::container::{Container, ScopeId};
194 ///
195 /// let container = Container::new();
196 /// container.scoped(|| RequestCache::new());
197 ///
198 /// // 请求 A(scope_id=1)
199 /// let cache_a1 = container.make_with_scope::<RequestCache>(1).unwrap();
200 /// let cache_a2 = container.make_with_scope::<RequestCache>(1).unwrap();
201 /// assert!(Arc::ptr_eq(&cache_a1, &cache_a2)); // 同一作用域:同一实例
202 ///
203 /// // 请求 B(scope_id=2)
204 /// let cache_b = container.make_with_scope::<RequestCache>(2).unwrap();
205 /// assert!(!Arc::ptr_eq(&cache_a1, &cache_b)); // 不同作用域:不同实例
206 ///
207 /// // 请求 A 结束
208 /// container.clear_scope(1);
209 /// ```
210 pub fn scoped<T, F>(&self, factory: F)
211 where
212 T: Send + Sync + 'static,
213 F: Fn() -> T + Send + Sync + 'static,
214 {
215 let type_id = TypeId::of::<T>();
216 let binding = ServiceBinding {
217 factory: Arc::new(move || Box::new(factory())),
218 lifetime: Lifetime::Scoped,
219 };
220 self.bindings.write().insert(type_id, binding);
221 }
222
223 /// 直接绑定已创建的实例(绕过工厂)
224 ///
225 /// 对齐 PHP `app()->instance('key', $obj)`。
226 ///
227 /// 将一个已创建的实例直接注册为单例,后续 `make` 返回此实例。
228 /// 适用于:
229 /// - 实例已在其他地方创建(如配置加载时初始化的服务)
230 /// - 实例创建过程复杂、不适合用闭包表达
231 /// - 测试中注入 mock 实例
232 ///
233 /// # 用法
234 ///
235 /// ```ignore
236 /// use sz_rust_core::container::Container;
237 ///
238 /// let container = Container::new();
239 /// let logger = Arc::new(FileLogger::new("/var/log/app.log"));
240 /// container.instance(logger.clone());
241 ///
242 /// let resolved = container.make::<FileLogger>().unwrap();
243 /// assert!(Arc::ptr_eq(&logger, &resolved));
244 /// ```
245 pub fn instance<T>(&self, instance: T)
246 where
247 T: Send + Sync + 'static,
248 {
249 let type_id = TypeId::of::<T>();
250 let arc: Arc<dyn Any + Send + Sync> = Arc::new(instance);
251 // 1. 缓存实例(make 会优先检查 instances 缓存)
252 self.instances.write().insert(type_id, arc);
253 // 2. 注册占位绑定(使 has() 返回 true)
254 // 注:factory 不会被调用,因为 make 会先命中 instances 缓存。
255 // 使用 unreachable 闭包表达此不变量;若被调用则说明内部状态被破坏。
256 self.bindings.write().insert(
257 type_id,
258 ServiceBinding {
259 factory: Arc::new(|| {
260 panic!("instance() 绑定的服务不应调用工厂 — 这是内部不变量违反")
261 }),
262 lifetime: Lifetime::Singleton,
263 },
264 );
265 }
266
267 /// 为服务类型注册字符串别名
268 ///
269 /// 对齐 PHP `app()->alias('name', Service::class)`。
270 ///
271 /// 别名仅用于:
272 /// - 调试输出([`Container::debug_aliases`] 列出所有别名)
273 /// - 反向查找([`Container::resolve_alias`] 通过别名获取 TypeId)
274 ///
275 /// 解析时仍用类型安全的 `make::<T>()`,不支持通过字符串别名解析
276 /// (Rust 类型系统要求编译时已知类型,字符串 key 解析会引入不安全的 downcast)。
277 ///
278 /// # 用法
279 ///
280 /// ```ignore
281 /// use sz_rust_core::container::Container;
282 ///
283 /// let container = Container::new();
284 /// container.singleton(|| MyService::new());
285 /// container.alias::<MyService>("my_service");
286 ///
287 /// assert!(container.is_alias("my_service"));
288 /// let type_id = container.resolve_alias("my_service").unwrap();
289 /// assert_eq!(type_id, std::any::TypeId::of::<MyService>());
290 /// ```
291 pub fn alias<T: 'static>(&self, name: impl Into<String>) {
292 let type_id = TypeId::of::<T>();
293 self.aliases.write().insert(name.into(), type_id);
294 }
295
296 /// 通过别名查找对应的 TypeId
297 ///
298 /// 返回 `None` 表示别名未注册。
299 pub fn resolve_alias(&self, name: &str) -> Option<TypeId> {
300 self.aliases.read().get(name).copied()
301 }
302
303 /// 检查指定别名是否已注册
304 pub fn is_alias(&self, name: &str) -> bool {
305 self.aliases.read().contains_key(name)
306 }
307
308 /// 列出所有已注册别名(用于调试)
309 pub fn debug_aliases(&self) -> Vec<String> {
310 self.aliases.read().keys().cloned().collect()
311 }
312
313 /// 解析服务实例(无作用域)
314 ///
315 /// 对齐 PHP `app()->make('key')`。
316 ///
317 /// 等价于 [`Container::make_with_scope`] 传入 `scope_id = 0`。
318 /// 对于 `Scoped` 生命周期服务,会使用 `scope_id = 0` 作为默认作用域。
319 ///
320 /// # 返回
321 ///
322 /// - `Some(Arc<T>)`:服务已注册,返回实例(单例返回缓存实例,瞬态返回新实例)
323 /// - `None`:服务未注册
324 ///
325 /// # Panics
326 ///
327 /// 理论上不会 panic(工厂返回的 `Box<dyn Any>` 内部类型由编译时泛型保证)。
328 /// 若发生 panic 说明内部状态被破坏(bindings 与 instances 不一致)。
329 pub fn make<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
330 self.make_with_scope::<T>(0)
331 }
332
333 /// 解析服务实例(带作用域 ID)
334 ///
335 /// 对齐 PHP `app()->make('key')` + 请求作用域支持。
336 ///
337 /// # 生命周期处理
338 ///
339 /// - `Singleton`:忽略 `scope_id`,返回全局缓存的单例
340 /// - `Transient`:忽略 `scope_id`,每次调用工厂创建新实例
341 /// - `Scoped`:同一 `scope_id` 内首次调用工厂创建并缓存,后续返回缓存
342 ///
343 /// # 返回
344 ///
345 /// - `Some(Arc<T>)`:服务已注册,返回实例
346 /// - `None`:服务未注册
347 ///
348 /// # Panics
349 ///
350 /// 理论上不会 panic(工厂返回的 `Box<dyn Any>` 内部类型由编译时泛型保证)。
351 /// 若发生 panic 说明内部状态被破坏。
352 pub fn make_with_scope<T: Send + Sync + 'static>(&self, scope_id: ScopeId) -> Option<Arc<T>> {
353 let type_id = TypeId::of::<T>();
354
355 // 1. 检查全局单例缓存(singleton 和 instance 都会写入此缓存)
356 if let Some(cached) = self.instances.read().get(&type_id) {
357 return Arc::downcast::<T>(cached.clone()).ok();
358 }
359
360 // 2. 检查作用域缓存(仅 Scoped 生命周期)
361 if scope_id != 0 {
362 let scoped = self.scoped_instances.read();
363 if let Some(scope_map) = scoped.get(&scope_id) {
364 if let Some(cached) = scope_map.get(&type_id) {
365 return Arc::downcast::<T>(cached.clone()).ok();
366 }
367 }
368 }
369
370 // 3. 查找绑定
371 // 注:先绑定 `let` 延长 `RwLockReadGuard` 生命周期,避免临时值被释放
372 let guard = self.bindings.read();
373 let binding = guard.get(&type_id)?.clone();
374 drop(guard); // 释放读锁后再调用工厂(避免持锁调用用户代码引发死锁/重入)
375
376 let instance = (binding.factory)();
377
378 match binding.lifetime {
379 Lifetime::Singleton => {
380 let arc: Arc<dyn Any + Send + Sync> = Arc::from(instance);
381 self.instances.write().insert(type_id, arc.clone());
382 Arc::downcast::<T>(arc).ok()
383 }
384 Lifetime::Scoped => {
385 let arc: Arc<dyn Any + Send + Sync> = Arc::from(instance);
386 self.scoped_instances
387 .write()
388 .entry(scope_id)
389 .or_default()
390 .insert(type_id, arc.clone());
391 Arc::downcast::<T>(arc).ok()
392 }
393 Lifetime::Transient => {
394 // 瞬态:直接返回(不缓存)
395 Arc::downcast::<T>(Arc::from(instance)).ok()
396 }
397 }
398 }
399
400 /// 清理指定作用域的所有缓存实例
401 ///
402 /// 应在请求结束时调用(如 axum 中间件在请求处理完毕后调用),
403 /// 释放该作用域内创建的所有 Scoped 服务实例。
404 ///
405 /// # 用法
406 ///
407 /// ```ignore
408 /// use sz_rust_core::container::Container;
409 ///
410 /// let container = Container::new();
411 /// container.scoped(|| RequestCache::new());
412 ///
413 /// let scope_id = generate_scope_id(); // 如从 axum State 获取
414 /// let _cache = container.make_with_scope::<RequestCache>(scope_id);
415 ///
416 /// // 请求结束
417 /// container.clear_scope(scope_id);
418 /// ```
419 pub fn clear_scope(&self, scope_id: ScopeId) {
420 self.scoped_instances.write().remove(&scope_id);
421 }
422
423 /// 检查服务是否已注册
424 pub fn has<T: 'static>(&self) -> bool {
425 let type_id = TypeId::of::<T>();
426 self.bindings.read().contains_key(&type_id)
427 }
428
429 /// 移除指定类型的服务绑定(含单例缓存与所有作用域缓存)
430 ///
431 /// 对齐 PHP `app()->remove('key')`。
432 pub fn forget<T: 'static>(&self) {
433 let type_id = TypeId::of::<T>();
434 self.bindings.write().remove(&type_id);
435 self.instances.write().remove(&type_id);
436 // 清理所有作用域中该类型的缓存
437 let mut scoped = self.scoped_instances.write();
438 for scope_map in scoped.values_mut() {
439 scope_map.remove(&type_id);
440 }
441 }
442
443 /// 清空所有服务绑定与缓存(含单例、作用域、别名)
444 pub fn clear(&self) {
445 self.bindings.write().clear();
446 self.instances.write().clear();
447 self.scoped_instances.write().clear();
448 self.aliases.write().clear();
449 }
450
451 /// 已注册服务数量(不含别名)
452 pub fn count(&self) -> usize {
453 self.bindings.read().len()
454 }
455
456 /// 已注册别名数量
457 pub fn alias_count(&self) -> usize {
458 self.aliases.read().len()
459 }
460
461 /// 当前活跃作用域数量
462 ///
463 /// 可用于检测作用域泄漏(如请求结束未调用 `clear_scope`)。
464 pub fn active_scope_count(&self) -> usize {
465 self.scoped_instances.read().len()
466 }
467}
468
469impl Default for Container {
470 fn default() -> Self {
471 Self::new()
472 }
473}
474
475impl std::fmt::Debug for Container {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 f.debug_struct("Container")
478 .field("bindings_count", &self.bindings.read().len())
479 .field("instances_count", &self.instances.read().len())
480 .field("scoped_scope_count", &self.scoped_instances.read().len())
481 .field("aliases_count", &self.aliases.read().len())
482 .finish()
483 }
484}
485
486// ============================================================================
487// App 容器(全局单例)
488// ============================================================================
489
490/// App 容器(全局单例)
491///
492/// 持有应用配置、各子系统单例和 DI 服务容器。通过 [`App::global()`] 获取全局实例,
493/// 通过 [`App::init()`] 初始化。
494pub struct App {
495 /// 应用配置(只读,初始化后不可变)
496 config: AppConfig,
497 /// 数据库连接配置(5 个:mysql/njszjt/ljclz/food/oceanbase)
498 /// 后续将替换为 SZ-ORM `Pool` 实例
499 db_connections: HashMap<String, DatabaseConnection>,
500 /// Cache 单例占位(接入真正的 Cache facade)
501 cache: RwLock<Option<String>>,
502 /// Log 单例占位(接入 sz-orm-logger + tracing)
503 log: RwLock<Option<String>>,
504 /// DI 服务容器(服务注册/解析/生命周期管理)
505 container: Container,
506}
507
508impl App {
509 /// 构造 App 实例(不注册到全局单例)
510 ///
511 /// 用于测试或显式持有实例的场景。生产代码应使用 [`App::init()`] 注册全局单例。
512 pub fn new(config: AppConfig) -> App {
513 let db_connections = config.database.connections.clone();
514 App {
515 config,
516 db_connections,
517 cache: RwLock::new(None),
518 log: RwLock::new(None),
519 container: Container::new(),
520 }
521 }
522
523 /// 初始化全局 App 容器
524 ///
525 /// 只能调用一次,重复调用返回已有实例。
526 ///
527 /// ```rust,ignore
528 /// use sz_rust_core::container::App;
529 /// use sz_rust_core::config::AppConfig;
530 ///
531 /// let config = AppConfig::load_from_dir("config").unwrap();
532 /// let app = App::init(config);
533 /// ```
534 pub fn init(config: AppConfig) -> &'static App {
535 APP.get_or_init(|| App::new(config))
536 }
537
538 /// 获取全局 App 容器实例
539 ///
540 /// 必须先调用 [`App::init()`] 初始化,否则返回 `None`。
541 ///
542 /// # 命名说明
543 ///
544 /// 此方法对应 PHP `app()` helper(获取全局容器实例)。
545 /// 不使用 `App::instance()` 是为了避免与 [`App::instance<T>`](绑定实例方法,
546 /// 对齐 PHP `app()->instance('key', $obj)`)冲突。
547 pub fn global() -> Option<&'static App> {
548 APP.get()
549 }
550
551 /// 获取应用配置
552 pub fn config(&self) -> &AppConfig {
553 &self.config
554 }
555
556 /// 获取数据库连接配置
557 ///
558 /// 对齐 PHP `Db::connect('mysql')`。
559 ///
560 /// 当前返回 `DatabaseConnection` 配置。
561 /// 后续将替换为 SZ-ORM `Pool` 实例。
562 pub fn db_connection(&self, name: &str) -> Option<&DatabaseConnection> {
563 self.db_connections.get(name)
564 }
565
566 /// 获取所有数据库连接名称
567 pub fn db_connection_names(&self) -> Vec<&str> {
568 self.db_connections.keys().map(|s| s.as_str()).collect()
569 }
570
571 /// 获取默认数据库连接配置
572 pub fn default_db_connection(&self) -> Option<&DatabaseConnection> {
573 self.db_connection(&self.config.database.default)
574 }
575
576 /// 设置 Cache 单例(将替换为真正的 Cache facade)
577 pub fn set_cache(&self, cache: impl Into<String>) {
578 let mut guard = self.cache.write();
579 *guard = Some(cache.into());
580 }
581
582 /// 获取 Cache 单例
583 pub fn cache(&self) -> Option<String> {
584 self.cache.read().clone()
585 }
586
587 /// 设置 Log 单例(将替换为真正的日志系统)
588 pub fn set_log(&self, log: impl Into<String>) {
589 let mut guard = self.log.write();
590 *guard = Some(log.into());
591 }
592
593 /// 获取 Log 单例
594 pub fn log(&self) -> Option<String> {
595 self.log.read().clone()
596 }
597
598 // ========================================================================
599 // DI 服务容器代理方法(对齐 PHP app()->bind/make/singleton/scoped/instance/alias)
600 // ========================================================================
601
602 /// 获取 DI 服务容器引用
603 pub fn container(&self) -> &Container {
604 &self.container
605 }
606
607 /// 注册瞬态服务
608 ///
609 /// 对齐 PHP `app()->bind('key', fn() => new Service())`。
610 pub fn bind<T, F>(&self, factory: F)
611 where
612 T: Send + Sync + 'static,
613 F: Fn() -> T + Send + Sync + 'static,
614 {
615 self.container.bind(factory);
616 }
617
618 /// 注册单例服务
619 ///
620 /// 对齐 PHP `app()->singleton('key', fn() => new Service())`。
621 pub fn singleton<T, F>(&self, factory: F)
622 where
623 T: Send + Sync + 'static,
624 F: Fn() -> T + Send + Sync + 'static,
625 {
626 self.container.singleton(factory);
627 }
628
629 /// 注册请求作用域服务
630 ///
631 /// 对齐 PHP `app()->scoped('key', fn() => new Service())`。
632 pub fn scoped<T, F>(&self, factory: F)
633 where
634 T: Send + Sync + 'static,
635 F: Fn() -> T + Send + Sync + 'static,
636 {
637 self.container.scoped(factory);
638 }
639
640 /// 直接绑定已创建的实例
641 ///
642 /// 对齐 PHP `app()->instance('key', $obj)`。
643 pub fn instance<T>(&self, instance: T)
644 where
645 T: Send + Sync + 'static,
646 {
647 self.container.instance(instance);
648 }
649
650 /// 为服务类型注册字符串别名
651 ///
652 /// 对齐 PHP `app()->alias('name', Service::class)`。
653 pub fn alias<T: 'static>(&self, name: impl Into<String>) {
654 self.container.alias::<T>(name);
655 }
656
657 /// 解析服务实例(无作用域)
658 ///
659 /// 对齐 PHP `app()->make('key')`。
660 pub fn make<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
661 self.container.make::<T>()
662 }
663
664 /// 解析服务实例(带作用域 ID)
665 ///
666 /// 对齐 PHP `app()->make('key')` + 请求作用域支持。
667 pub fn make_with_scope<T: Send + Sync + 'static>(&self, scope_id: ScopeId) -> Option<Arc<T>> {
668 self.container.make_with_scope::<T>(scope_id)
669 }
670
671 /// 清理指定作用域的所有缓存实例
672 pub fn clear_scope(&self, scope_id: ScopeId) {
673 self.container.clear_scope(scope_id);
674 }
675
676 /// 检查服务是否已注册
677 pub fn has_service<T: 'static>(&self) -> bool {
678 self.container.has::<T>()
679 }
680}
681
682impl std::fmt::Debug for App {
683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684 f.debug_struct("App")
685 .field("config", &self.config)
686 .field(
687 "db_connections",
688 &self.db_connections.keys().collect::<Vec<_>>(),
689 )
690 .field("cache", &self.cache.read().is_some())
691 .field("log", &self.log.read().is_some())
692 .field("container", &self.container)
693 .finish()
694 }
695}
696
697// ============================================================================
698// 单元测试(分离到 tests.rs,降低单文件认知负担)
699// ============================================================================
700
701#[cfg(test)]
702mod tests;