Skip to main content

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;