You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

函数驱动型应用(Drupal 7)中PHP依赖注入容器的适配问题

这确实是Drupal 7这类老旧过程式架构项目里很常见的痛点——一边想用上现代DI的好处,一边又没法摆脱大量现存的函数代码。我来分享几个实用的方案,帮你把DI容器和这些函数打通:

方案1:用静态持有类让全局可访问容器(兼顾测试性)

既然Drupal 7的函数无法通过构造函数注入依赖,我们可以把容器做成全局可访问但可控的实例,避免直接用全局变量(太乱),而是用一个静态类来持有它:

首先在你的自定义模块初始化时创建并注入容器:

// 在你的自定义模块的 .module 文件中
function mymodule_init() {
    // 初始化Pimple容器(换成PHP-DI逻辑也一样)
    $container = new Pimple\Container();
    $container['my_service'] = function($c) {
        return new MyService($c['core_dependency']);
    };
    $container['core_dependency'] = function() {
        return new CoreDependency();
    };

    // 用静态类持有容器,比全局变量更易控制
    MyAppContainer::set($container);
}

// 自定义一个极简的容器持有类
class MyAppContainer {
    private static $container;

    public static function set(Pimple\Container $container) {
        self::$container = $container;
    }

    public static function get(): Pimple\Container {
        return self::$container;
    }

    // 可选:封装get方法,直接获取服务,减少重复代码
    public static function service(string $id) {
        return self::$container[$id];
    }
}

然后在任意Drupal函数里就可以这样用:

function my_drupal_user_function($uid) {
    // 直接获取容器里的服务
    $userService = MyAppContainer::service('user_processing_service');
    return $userService->handleUserLogic($uid);
}

测试时的优势:你可以在测试的setUp阶段替换容器里的模拟对象,完全隔离依赖:

public function testMyDrupalUserFunction() {
    // 用Prophecy创建mock服务
    $mockUserService = Prophecy::prophesize(UserProcessingService::class)->reveal();
    $mockUserService->handleUserLogic(123)->willReturn('mocked_result');

    // 临时替换容器里的服务
    $testContainer = new Pimple\Container();
    $testContainer['user_processing_service'] = fn() => $mockUserService;
    MyAppContainer::set($testContainer);

    // 调用函数并断言结果
    $result = my_drupal_user_function(123);
    $this->assertEquals('mocked_result', $result);
}

方案2:逐步重构,让函数成为服务的“薄入口”

如果不想让函数直接依赖容器,更优雅的方式是把函数里的核心逻辑迁移到服务类,只留函数作为调用服务的入口层:

比如原来的函数是这样的:

function old_drupal_node_save_function($node_data) {
    // 一堆复杂的业务逻辑,混杂Drupal API调用
    $processed_data = do_complex_processing($node_data);
    node_save($processed_data);
    send_notification($node_data['uid']);
}

重构后:

function old_drupal_node_save_function($node_data) {
    // 函数只做一件事:获取服务并调用方法
    $nodeService = MyAppContainer::service('node_management_service');
    return $nodeService->saveAndNotify($node_data);
}

// 对应的服务类(完全可通过构造函数注入依赖)
class NodeManagementService {
    private $notificationService;

    public function __construct(NotificationService $notificationService) {
        $this->notificationService = $notificationService;
    }

    public function saveAndNotify(array $node_data) {
        $processed_data = $this->processNodeData($node_data);
        node_save($processed_data); // 保留必要的Drupal API调用
        $this->notificationService->sendToUser($node_data['uid']);
    }

    private function processNodeData(array $data) {
        // 原来的复杂逻辑移到这里,可单独测试
        return $data;
    }
}

这种方式的好处是:

  • 核心业务逻辑完全在可测试的类里,函数只是无逻辑的入口
  • 可以逐步重构,不用一次性改完所有函数
  • 保持了DI容器的规范使用,避免函数里到处取服务

方案3:解决PHP-DI的自动注入问题(如果你想换回它)

你之前说PHP-DI初次尝试没成功解析依赖,大概率是初始化或配置的问题。试试在Drupal初始化时正确构建容器:

function mymodule_init() {
    $builder = new DI\ContainerBuilder();
    // 加载DI配置文件(可以是数组或PHP文件)
    $builder->addDefinitions(__DIR__ . '/di-config.php');
    // 构建容器
    $container = $builder->build();
    MyAppContainer::set($container);
}

di-config.php里的配置示例:

return [
    MyService::class => DI\autowire(),
    Dependency::class => DI\create(),
    // 如果需要依赖Drupal的全局对象,可以用工厂方法
    'drupal_database' => DI\factory(fn() => Database::getConnection()),
];

这样你就可以用MyAppContainer::get()->get(MyService::class)来获取自动注入的实例,类型更安全。

关键注意事项

  • 不要把容器当成“万能工具”滥用:尽量让类通过构造函数注入依赖,函数只作为入口获取服务
  • 封装Drupal核心依赖:比如把node_load()、user_load()这类函数封装成服务,注入到业务类里,方便测试时mock
  • 避免在函数里直接实例化类:所有实例化都交给容器,保持依赖的可控性

内容的提问来源于stack exchange,提问作者artfulrobot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 11:01:24