Doctrine ODM:Symfony中动态创建DocumentManager适配多客户MongoDB
解决方案与架构建议
核心实现方案
1. 构建租户专属DocumentManager工厂
创建一个工厂类,负责根据数据库名动态生成对应租户的DocumentManager,基于默认配置克隆修改,避免污染全局配置:
namespace App\Service; use Doctrine\ODM\MongoDB\Configuration; use Doctrine\ODM\MongoDB\DocumentManager; use MongoDB\Client; class TenantDocumentManagerFactory { private Configuration $baseConfig; private Client $mongoClient; public function __construct(Configuration $baseConfig, Client $mongoClient) { // 克隆默认配置,避免修改全局实例 $this->baseConfig = clone $baseConfig; $this->mongoClient = $mongoClient; } // 缓存已创建的DocumentManager,避免同一请求重复初始化 private array $dmCache = []; public function createForDatabase(string $databaseName): DocumentManager { if (isset($this->dmCache[$databaseName])) { return $this->dmCache[$databaseName]; } $config = clone $this->baseConfig; $config->setDefaultDB($databaseName); $dm = DocumentManager::create($this->mongoClient, $config); $this->dmCache[$databaseName] = $dm; return $dm; } }
2. 实现上下文感知的DocumentManager代理
创建代理类,自动从当前请求中获取租户信息,并动态路由到对应的DocumentManager,替换默认的DI服务:
namespace App\Service; use Doctrine\ODM\MongoDB\DocumentManager; use Symfony\Component\HttpFoundation\RequestStack; class TenantAwareDocumentManager extends DocumentManager { private TenantDocumentManagerFactory $factory; private RequestStack $requestStack; private ?DocumentManager $actualDm = null; public function __construct(TenantDocumentManagerFactory $factory, RequestStack $requestStack) { // 父类构造传空,所有操作代理到实际实例 parent::__construct(null, null); $this->factory = $factory; $this->requestStack = $requestStack; } // 代理所有未重写的方法 public function __call(string $name, array $arguments) { return $this->getActualDm()->$name(...$arguments); } // 重写核心方法避免__call的性能损耗 public function getRepository($documentName) { return $this->getActualDm()->getRepository($documentName); } public function persist($object) { $this->getActualDm()->persist($object); } public function flush() { $this->getActualDm()->flush(); } private function getActualDm(): DocumentManager { if (!$this->actualDm) { $request = $this->requestStack->getCurrentRequest(); // 从请求中获取租户数据库名(示例:路由参数、请求头、会话均可) $dbName = $request->attributes->get('tenant_db'); $this->actualDm = $this->factory->createForDatabase($dbName); } return $this->actualDm; } }
3. 配置DI替换默认服务
在services.yaml中用代理类装饰默认的DocumentManager,实现全局无缝替换:
services: App\Service\TenantDocumentManagerFactory: arguments: $baseConfig: '@doctrine_mongodb.odm.default_configuration' $mongoClient: '@doctrine_mongodb.odm.default_connection' App\Service\TenantAwareDocumentManager: decorates: doctrine_mongodb.odm.default_document_manager arguments: $factory: '@App\Service\TenantDocumentManagerFactory' $requestStack: '@request_stack' public: false
适用的设计模式
- 工厂模式:
TenantDocumentManagerFactory封装DocumentManager的创建逻辑,统一管理实例缓存与配置克隆规则。 - 代理模式:
TenantAwareDocumentManager作为代理,隐藏动态切换租户的细节,让上层服务无需修改代码即可适配多租户场景。 - 上下文对象模式:通过
RequestStack获取当前请求的租户上下文,将上下文信息传递给工厂生成对应服务实例。 - 装饰器模式:利用Symfony的DI装饰器机制,无缝替换默认的DocumentManager服务,无需修改原有服务的依赖配置。
注意事项
- 元数据缓存:由于所有租户schema一致,可复用全局元数据缓存,避免每个DocumentManager重复加载元数据。
- 请求生命周期清理:Symfony的请求作用域会自动销毁代理实例,但若自定义了缓存,需在请求结束时清理缓存避免内存泄漏。
- 多租户标识获取:需根据业务场景选择合适的租户标识传递方式(如路由参数、请求头、JWT令牌等)。
内容的提问来源于stack exchange,提问作者user3953201
相关产品推荐
相关产品推荐

