接口在etc/di.xml已经配置preference,前台和CLI也能正常运行,但setup:di:compile或后台请求仍报Cannot instantiate interface。后来发现另一个模块在etc/adminhtml/di.xml里给同一个构造参数注入了接口名,同时用同名virtualType覆盖了全局定义。DI配置会按area合并,不能只看全局文件。

先查接口和参数的所有声明

grep -R "Vendor\\Api\\DataProviderInterface" -n app/code vendor/*/*/etc --include='di.xml'
grep -R 'name="targetArgument"' -n app/code vendor/*/*/etc --include='di.xml'

同时检查etc/di.xml、etc/adminhtml/di.xml和etc/frontend/di.xml。一个area文件不一定直接删除全局preference,但可以通过type arguments或virtualType把实际依赖换成另一个未配置接口。

用di:info看最终结果

bin/magento dev:di:info "Vendor\Module\Model\Target"
bin/magento dev:di:info "Vendor\Api\DataProviderInterface"

某些版本dev:di:info不方便指定area,这时可以在开发环境生成合并配置或写小型集成测试,在adminhtml area加载ObjectManager后解析目标类。不要在生产HTTP请求里直接调用ObjectManager做调试。

常见的四种根因

  • preference只写在frontend/di.xml,Adminhtml无法看到
  • virtualType name与另一个模块重名,后加载模块覆盖参数
  • 构造参数类型改了,旧generated metadata仍引用旧接口
  • 模块sequence缺失,配置合并顺序与预期不同

修复方式

通用实现应放全局etc/di.xml;只有后台需要不同实现时,才在adminhtml area覆盖preference或目标type参数。virtualType使用带模块前缀的唯一名称,避免泛化名称如CustomDataProvider。

<preference for="Vendor\Api\DataProviderInterface"
            type="Vendor\Module\Model\DataProvider" />

<virtualType name="VendorModuleAdminDataProvider" type="Vendor\Module\Model\DataProvider">
  <arguments>...</arguments>
</virtualType>

如果确实依赖另一个模块的DI声明,在module.xml中添加sequence,但不要用sequence掩盖循环依赖。更好的结构是把公共接口和默认实现放在明确的基础模块。

清理并重新编译

rm -rf generated/code/* generated/metadata/*
composer dump-autoload -o
bin/magento setup:di:compile
bin/magento cache:clean config

回归

  • 后台目标页面可以实例化并完成操作
  • CLI命令与Cron仍使用预期实现
  • 前台没有被后台专用实现污染
  • 禁用可选模块后DI仍有明确默认实现
  • CI分别覆盖global、frontend和adminhtml集成测试
看到Cannot instantiate interface,不要只机械补preference。先找出究竟是哪一个area、哪一个virtualType把接口送进了构造函数。