开发跨CMS PHP库:PrestaShop中Symfony DI容器合并方案咨询
我正在开发一款适配多CMS(WordPress、Drupal、PrestaShop等)的PHP库。不同CMS特性差异明显:PrestaShop支持Symfony依赖注入(DI)系统,而WordPress没有这类机制,通常用单例模式实现。
随着库的复杂度提升,依赖注入/容器系统变得不可或缺。但在PrestaShop场景下遇到了瓶颈:需要执行模块特有的数据库调用,因此要获取PrestaShop容器中定义的服务,但找不到在库容器中获取该服务的「干净」方式。
当前库中容器的创建代码如下:
private function createContainer() { $container = new ContainerBuilder(); $configurationLoader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Ressources/config')); $configurationLoader->load('services.yml'); $container->compile(); return $container; }
我曾尝试通过以下方式注入PrestaShop的WidgetRepositoryInterface实例,但当使用PhpDumper缓存库容器时,该方案失效:
private function createContainer(WidgetRepositoryInterface $widgetRepository) { $container = new ContainerBuilder(); $container->set('repository.widget_repository', $widgetRepository); $container->setAlias(WidgetRepositoryInterface::class, 'repository.widget_repository'); $configurationLoader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Ressources/config')); $configurationLoader->load('services.yml'); $container->compile(); return $container; }
我试过这些思路但没解决问题:
- 扩展系统
- 库中的编译器传递
- 从PrestaShop模块文件导入库的services.yml文件
希望能得到可行的解决方案。
方法1:复用PrestaShop容器(推荐)
不要在库中创建独立容器,直接复用PrestaShop的Symfony容器加载库的服务配置。这样库的服务可以直接引用PrestaShop容器中的现有服务,缓存时也能完整保留依赖关系。
示例代码:
public function registerLibraryToPrestaShopContainer(ContainerInterface $psContainer): void { if (!$psContainer instanceof ContainerBuilder) { throw new \RuntimeException('PrestaShop container must be a ContainerBuilder instance to load library services'); } // 加载库的服务配置到PrestaShop容器 $loader = new YamlFileLoader($psContainer, new FileLocator(__DIR__ . '/../Ressources/config')); $loader->load('services.yml'); // 绑定库的接口到PrestaShop容器中的对应服务 $psContainer->setAlias(WidgetRepositoryInterface::class, 'prestashop.repository.widget'); // 重新编译容器(如果PrestaShop还没完成编译) if (!$psContainer->isCompiled()) { $psContainer->compile(); } }
优势:完全遵循Symfony DI的设计逻辑,避免容器拆分带来的缓存问题,同时简化库的初始化流程。
方法2:用编译器传递同步外部服务
如果必须保留库的独立容器,可以通过编译器传递在容器编译阶段,将PrestaShop容器中的服务定义同步到库容器中,确保缓存时能正确识别这些依赖。
步骤1:实现编译器传递类
class SyncPrestaShopServicesPass implements CompilerPassInterface { private array $requiredServiceIds; public function __construct(array $requiredServiceIds) { $this->requiredServiceIds = $requiredServiceIds; } public function process(ContainerBuilder $container): void { // 获取PrestaShop全局容器实例(根据实际情况调整获取方式) $psContainer = \PrestaShop\PrestaShop\Core\Container::getInstance(); foreach ($this->requiredServiceIds as $serviceId) { if ($psContainer->has($serviceId)) { // 复制PrestaShop的服务定义到库容器 $psDefinition = $psContainer->findDefinition($serviceId); $container->setDefinition($serviceId, $psDefinition); } else { throw new \RuntimeException("Required PrestaShop service $serviceId not found"); } } // 绑定库的接口到同步过来的服务 $container->setAlias(WidgetRepositoryInterface::class, 'prestashop.repository.widget'); } }
步骤2:修改库容器创建逻辑
private function createContainer(): ContainerBuilder { $container = new ContainerBuilder(); // 加载库自身的服务配置 $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Ressources/config')); $loader->load('services.yml'); // 添加编译器传递,同步所需的PrestaShop服务 $container->addCompilerPass(new SyncPrestaShopServicesPass([ 'prestashop.repository.widget', // 其他需要的PrestaShop服务ID ])); $container->compile(); return $container; }
注意:必须保证PrestaShop容器在库容器编译前已经完成初始化,否则会找不到服务定义。
方法3:用工厂类延迟获取服务
如果直接注入实例导致缓存失效,可以通过工厂类延迟从PrestaShop容器获取服务,容器缓存时只会保存工厂的定义,而非具体实例。
步骤1:创建服务工厂
class WidgetRepositoryFactory { public function create(): WidgetRepositoryInterface { // 从PrestaShop容器获取服务实例 return \PrestaShop\PrestaShop\Core\Container::getInstance()->get('prestashop.repository.widget'); } }
步骤2:修改库的services.yml配置
services: # 注册工厂类 app.widget_repository_factory: class: App\Factory\WidgetRepositoryFactory # 将接口绑定到工厂的create方法 App\Repository\WidgetRepositoryInterface: factory: ['@app.widget_repository_factory', 'create']
这样库容器编译缓存时,只会记录工厂的定义,实际使用服务时才会从PrestaShop容器获取实例,避免缓存时丢失外部服务引用的问题。
内容的提问来源于stack exchange,提问作者Thomas DELAPORTE

