setup:di:compile 报 Cannot instantiate interface 时,不要直接给报错接口随便写一个 preference。这个异常只说明 Object Manager 在当前作用域无法把接口变成具体类,根因可能是模块没加载、配置被覆盖,甚至是构造函数写错了类型。

先沿着异常栈找到是谁需要这个接口

报错最后一行常常只有接口名,真正有价值的是上面第一个自定义类。查看它的构造函数,确认参数类型是不是你预期的接口。有时 IDE 自动导入了同名但不同命名空间的 Interface,怎么改 di.xml 都不会对。

bin/magento setup:di:compile -vvv
bin/magento module:status Vendor_Module
grep -R "完整接口名" app/code vendor/*/*/etc -n

搜索结果里重点看 preference、type argument 和 virtualType。一个接口可以在全局 etc/di.xml 有实现,但某个 etc/adminhtml/di.xml 或 etc/frontend/di.xml 又改了参数。CLI 编译读取的配置范围和浏览器请求并不完全一样,所以“后台能用”不能证明全局 DI 正确。

再看异常发生在 application code generation 的哪一步。Repositories、Factories、Proxies 和 Interceptors 的错误上下文不同:Factory 生成失败经常是目标类或构造函数不可用;Interceptor 失败则要检查被插件的方法是否允许拦截、插件类本身能否实例化。不要只复制异常最后一句去搜索。

检查提供实现的模块是否真的在依赖链里

如果实现来自另一个模块,调用方的 module.xml 应通过 sequence 表明加载关系,composer.json 也应声明包依赖。只依赖服务器上碰巧安装的模块,换一套环境就会在编译阶段暴露出来。

另外核对具体类是否可实例化:是不是 abstract、构造函数又依赖了同一个接口形成循环、类文件大小写是否和 Linux 文件系统一致。开发机在不区分大小写的文件系统上能跑,并不代表生产机也能自动找到。

用生成后的DI信息验证,不靠清缓存碰运气

修改后删除的应该是可重新生成的编译产物,并按照项目部署流程重新编译;不要把 vendor 和配置文件一起删掉。编译通过后,再用 DI 信息命令确认接口最终解析到哪个类:

bin/magento dev:di:info 'VendorModuleApiSomeInterface'

如果这个命令得到的实现和设计不一致,继续查覆盖顺序。只有确认“谁请求接口、哪个配置提供实现、最终合并结果是什么”,这个问题才算解决。临时把接口改成具体类虽然可能过编译,却会破坏可替换性,也容易在下次升级时再次爆掉。

发布前还要在一套干净目录执行 composer install 后重新编译。开发机残留的 generated 文件可能让错误暂时消失,CI 的全新工作区更能证明依赖声明与 DI 配置完整。若只有生产报错,比较启用模块列表和 composer.lock,而不是把生产生成目录复制回开发机。