ThinkPHP 8新特性需按需使用:多应用绑定须用App::getInstance()->bind()或Container::getInstance()->bind();协程需php think swoole启动并启用enable_coroutine;嵌套事务须显式传true且引擎为InnoDB;模板默认禁用php表达式,需配置tpl_deny_php或改用{php}标签。

ThinkPHP 8 的新特性不是拿来“堆砌”的,而是为了解决具体问题才用的——比如你正被多应用路由混乱困扰,或想让命令行任务支持协程,那才值得动它。盲目升级或照搬文档写法,反而容易掉进默认策略变更的坑里。
多应用绑定为什么 app()->bind() 失效了
TP8 把应用生命周期和容器解耦了,app()->bind() 在非主应用里大概率不生效,因为绑定发生在主应用容器,子应用拿不到。
- 改用
App::getInstance()->bind()显式获取当前应用实例再绑定 - 更稳妥的做法是:在子应用的
bootstrap.php里调用Container::getInstance()->bind() - 别在全局
common.php里写bind(),那里没应用上下文,绑定后可能被后续应用覆盖 - 如果绑定的是控制器或中间件类,注意它们的解析时机是否早于容器初始化
php think run 启动不了协程服务
TP8 默认命令行不启用协程支持,think run 走的是传统 Swoole HTTP server 启动流程,不是 swoole_http_server 或 co\run。
- 必须手动安装 Swoole 扩展(v5.0+),且 PHP 编译时不能禁用
--enable-coroutine - 启动命令要换成
php think swoole,并确保配置中'swoole' => ['enable_coroutine' => true] - 协程内不能用
sleep()、file_get_contents()等同步阻塞函数,得换co::sleep()、co::readFile() -
Db::transaction()在协程里嵌套事务必须显式传true,否则会报Transaction already started
模板里 {:func()} 突然不执行了
TP8 模板编译器默认关闭了 PHP 表达式执行,tpl_deny_php 默认为 true,{:func()} 这类动态调用会被直接忽略或报错。
- 临时方案:在
config/template.php中设'tpl_deny_php' => false(仅开发环境) - 推荐做法:把逻辑提到控制器,用
assign()传变量,模板里只做展示 - 如果真要保留表达式,改用
{php}echo func();{/php},但需确认tpl_replace_string配置未禁用php标签 - 注意:即使开了
tpl_deny_php,eval()、system()等危险函数仍被底层过滤,不是开了就能任意执行
Db::transaction() 嵌套事务报 RuntimeException
TP8 的事务底层改用 PDO 的savepoint 实现嵌套,但默认不开启,直接嵌套会抛出 RuntimeException: Transaction already started。
- 外层事务必须显式传
true:Db::transaction(fn() => ..., true) - 内层用
Db::transactionLevel()判断当前层级,避免重复commit/rollback - MySQL 必须是 InnoDB 引擎,且隔离级别不能是
READ UNCOMMITTED,否则savepoint无效 - 注意:嵌套事务在 CLI 和 Web 请求下行为一致,但在协程环境下,若混用同步 I/O(如
file_get_contents),可能导致事务上下文丢失
最容易被忽略的,是那些“以前能跑,现在不报错但结果不对”的情况——比如模板变量渲染为空、事务回滚没生效、协程任务卡住却不报错。这些往往不是 Bug,而是 TP8 把默认安全边界收得更紧了。



















