Yii 3迁移是重写而非升级:需PHP 8.2+、弃用全局实例与隐式访问、改用PSR-11容器显式注入、重构路由为中间件+属性路由、删除所有Yii 1.1加载机制与钩子。

跨版本从Yii 1.1迁移到Yii 3项目部署不是升级,而是重写——你得放弃所有魔性语法、全局应用实例和隐式服务访问,用PHP 8.2+环境、PSR-11容器、显式注入和模块化包重新搭建整个应用结构。直接复制旧代码或沿用Yii 1.1的入口文件、配置方式、路由规则,会在composer install阶段就报错退出。
第一步:确认并锁定PHP运行环境
执行php -v,输出必须为PHP 8.2.x或更高(如8.3.7),低于8.2会立即失败,不会降级兼容。
【PHP 8.2是硬性门槛,不是建议】 Yii 3源码中大量使用构造器属性提升(public string $name)、联合类型(string|int|null)、#[\Attribute]元数据和mixed声明,PHP 8.1及以下无法解析这些语法。
CI/CD流水线中必须显式指定镜像,例如php:8.3-apache或php:8.2-cli;用php:8.1拉取会静默失败,不提示、不回退、不创建任何文件。
第二步:废弃所有Yii 1.1遗产路径与加载机制
删除framework/目录、protected/目录、index.php中require_once('framework/yii.php')这类手动加载逻辑。
Yii 3不再识别YII_PATH常量、Yii::createObject()、Yii::app()->db、CMap::mergeArray等Yii 1.1/2.x惯用写法——它们已被彻底移除,调用即Fatal error。
不要尝试保留旧main.php配置文件并“改造”它。Yii 3的配置统一放在config/common.php中,格式为接口→实现映射:LoggerInterface::class => ['class' => FileLogger::class],不支持YAML、XML或嵌套数组合并。
第三步:用Composer重建项目骨架
在空目录下执行:composer create-project yiisoft/app myproject。
这一步会自动安装130+个独立包(如yiisoft/router、yiisoft/di、yiisoft/view),生成符合PSR-15中间件协议的public/index.php入口,并注册vendor/autoload.php作为唯一自动加载源。
严禁手动拷贝Yii 1.1的framework文件夹进去——这会导致PSR-4自动加载规则冲突,Class not found错误频发,且Gii、Debug Toolbar等扩展根本无法注册,因为它们依赖composer.json中的require-dev联动机制。
第四步:重构控制器与依赖注入方式
方法一:构造函数注入(强制要求)
将原Yii 1.1中$this->db、$this->cache等隐式访问,全部改为构造参数声明:
public function __construct(private DbConnectionInterface $db, private CacheInterface $cache) {}
方法二:配置绑定接口实现
在config/common.php中注册具体实现:DbConnectionInterface::class => ['class' => PgsqlConnection::class, 'dsn' => getenv('DB_DSN')],
【不能在方法体内调用Yii::getContainer()->get()】 这种写法破坏依赖可测性,Yii 3禁止在任意方法中动态获取容器实例,所有依赖必须在构造时明确声明。
第五步:重写请求生命周期与路由
第一步:删掉所有beforeAction、afterAction、init()钩子——Yii 3的yii\web\Application不再继承yii\base\Application,而是实现PSR-15协议,整个流程走中间件栈。
第二步:权限控制、日志记录、响应包装等逻辑,必须拆成独立中间件类,实现MiddlewareInterface,并在config/web.php中注册到中间件链。
第三步:路由不再自动映射actionIndex到/index,必须显式配置或使用属性路由:#[Route('GET /')] public function index(): ResponseInterface { ... }
第四步:Yii::$app->response变为只读的ResponseInterface实例,不能直接赋值status或headers,需返回新响应对象:return $this->response->withStatus(404)->withHeader('X-Reason', 'Not Found');


















