Upgrading from Version 1.1
If you haven't used Yii 1.1, you can skip this section and get directly to "getting started" section.
Yii3 is not an incremental upgrade from Yii 1.1. Yii 2.0 was a full rewrite, and Yii3 changes the architecture again: Composer packages, namespaces, PHP 8, PSR interfaces, dependency injection, explicit routes, middleware, and separate packages for features that were built into the framework in Yii 1.1.
Treat this as building a new Yii3 application with the benefit of Yii 1.1 experience. Reuse the domain knowledge, database schema, user workflows, and selected code after refactoring. Do not try to reproduce the Yii 1.1 application structure in Yii3. A direct search-and-replace conversion is not realistic.
For additional background, read the Yii 2.0 guide section Upgrading from Version 1.1. It explains the first major break from Yii 1.1: Composer, namespaces, object configuration, view changes, controller return values, widgets, assets, helpers, forms, query builder, Active Record, and URL management. Yii3 builds on many of those changes and makes dependencies more explicit.
Recommended approach
Choose one of these paths:
- Build a new Yii3 application directly when the Yii 1.1 application is small or you are already rewriting request flows.
- Move Yii 1.1 concepts to Yii 2.0 first when the team needs a smaller conceptual step.
- Keep Yii 1.1 running and rebuild critical flows in Yii3 one by one.
For most long-lived applications, the third option is the least risky. Create a Yii3 application, connect it to the same database, and build vertical slices: route, action, input validation, persistence, view, assets, and tests. Each slice should be idiomatic Yii3, even when it is based on behavior from the Yii 1.1 application.
Prepare the Yii 1.1 application
Useful preparation can happen before the Yii3 application is ready. These refactorings make old behavior easier to understand and reduce the amount of Yii 1.1 framework state that crosses into the new code:
- Remove direct
Yii::app()access from reusable code where practical. If a full refactoring is too large, keepYii::app()calls near controllers, widgets, and commands, then pass values and services to lower-level code explicitly. - Introduce repositories or query services for database reads currently spread across controllers, views,
CActiveRecordmethods, and widgets. Yii3 can use Active Record, but it does not require it for every persistence task. - Separate business rules from framework infrastructure. Code that does not extend
CComponent, read global application state, or render views is easier to reuse as a specification for Yii3 code. - Move reusable behavior out of large controllers and Active Record classes into Yii 1.1 application components or plain services. Yii3 services are configured and injected differently, but the responsibility boundary is similar.
- Add tests or characterization checks around routes and workflows you plan to rebuild first. They become the executable specification for the Yii3 slice.
For example, instead of reading post archive data directly from several controllers or widgets, create a small service that describes the application need:
php
class PostRepository extends CComponent
{
public function getArchive()
{
// ...
}
public function getTopForFrontPage($limit = 10)
{
// ...
}
}The Yii3 implementation may use yiisoft/active-record, yiisoft/db, or another persistence layer. The important point is that controllers and views no longer need to know that detail.
Things to learn before starting
Some Yii3 application-template choices are likely to be new in a Yii 1.1 codebase:
- Composer. Yii3 applications and packages are installed through Composer and loaded with PSR-4 autoloading.
- Namespaces. Yii3 classes are namespaced instead of using Yii 1.1-style class prefixes and path aliases for class loading.
- Docker. The default Yii3 application template includes Docker configuration. Using it gives each application its own repeatable environment and reduces differences between local development and production.
- Environment variables. Yii3 templates commonly use them for environment-specific settings and secrets, especially in Docker-based deployments. This follows the same idea as 12-factor app configuration.
- Actions. Yii3 does not require controller inheritance. A route can point to any callable, and the default template uses invokable action classes.
- Application structure. Yii3's default structure is different from Yii 1.1. Start with the Yii3 template and change it only when the application needs it.
PHP, Composer, and namespaces
Yii 1.1 applications commonly rely on PHP 5-era code, class prefixes such as CController, and autoloading by class name. Yii3 applications require modern PHP and Composer autoloading.
Yii 1.1:
php
class SiteController extends CController
{
}Yii3:
php
namespace App\Web\HomePage;
final readonly class Action
{
}Configure your application namespace with PSR-4:
json
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}Run:
shell
composer dump-autoloadWhen reusing a class, give it a namespace, add strict types, replace Yii 1.1 base classes, and inject dependencies instead of reading them from global application state. If these changes make the class awkward, use the old class as a specification and write a new Yii3 class instead.
Project structure
A Yii 1.1 application often uses:
text
protected/
commands/
components/
config/
controllers/
extensions/
messages/
migrations/
models/
views/
themes/
webroot/The current Yii3 application template uses:
text
assets/
config/
public/
runtime/
src/
tests/Typical mappings to consider are:
| Yii 1.1 | Yii3 |
|---|---|
protected/controllers | src/Web/*/Action.php or controller-like services |
protected/models | src/Domain, src/Shared, src/Db, or form models by responsibility |
protected/views | templates near the feature, for example src/Web/HomePage/template.php |
protected/views/layouts | layout templates and assets under src/Web/Shared/Layout |
protected/components | services configured in the dependency injection container |
protected/extensions | Composer packages or local packages |
protected/commands | src/Console/*Command.php |
protected/messages | translator message files |
protected/config | config/common, config/web, config/console, config/environments |
webroot | public |
Do not recreate protected in Yii3. Put PHP source under src, public files under public, generated/runtime files under runtime, and configuration under config.
Configuration and application components
Yii 1.1 configuration is usually one large array:
php
return [
'basePath' => dirname(__DIR__),
'components' => [
'db' => [
'connectionString' => 'mysql:host=localhost;dbname=app',
'username' => 'root',
'password' => '',
],
'urlManager' => [
'urlFormat' => 'path',
'rules' => [
'post/<id:\d+>' => 'post/view',
],
],
],
];In Yii3, configuration is split by purpose and services are wired through the container:
- aliases in
config/common/aliases.php; - parameters in
config/common/params.phpand environment files; - shared service definitions in
config/common/di/*.php; - web-only service definitions in
config/web/di/*.php; - routes in
config/common/routes.php; - console commands in
config/console/*.php.
The default template's console DI group reuses shared definitions from config/common/di/*.php. If you need console-only container definitions, add files for them to the di-console group in config/configuration.php and rebuild the merge plan.
Replace Yii::app()->componentName with constructor-injected services:
php
use Yiisoft\Db\Connection\ConnectionInterface;
use Yiisoft\Router\UrlGeneratorInterface;
final readonly class PostRepository
{
public function __construct(
private ConnectionInterface $db,
private UrlGeneratorInterface $urlGenerator,
) {
}
}See Configuration, Aliases, and Dependency injection container.
Entry script
Yii 1.1 entry scripts commonly load yii.php and create a web application:
php
require_once __DIR__ . '/../framework/yii.php';
Yii::createWebApplication($config)->run();Yii3 entry scripts load Composer and bootstrap the configured application. Start with the public/index.php file from yiisoft/app and adjust configuration instead of building the application manually in the entry script.
See Entry scripts and Application workflow.
Controllers, actions, and routes
Yii 1.1 controllers extend CController, and actions are methods named actionName:
php
class PostController extends CController
{
public function actionView($id)
{
$post = Post::model()->findByPk($id);
if ($post === null) {
throw new CHttpException(404);
}
$this->render('view', ['post' => $post]);
}
}Yii3 does not require controller inheritance. The current template uses invokable action classes:
php
namespace App\Web\Post\View;
use App\Post\PostRepository;
use Psr\Http\Message\ResponseInterface;
use Yiisoft\Router\CurrentRoute;
use Yiisoft\Yii\View\Renderer\WebViewRenderer;
final readonly class Action
{
public function __construct(
private CurrentRoute $currentRoute,
private PostRepository $posts,
private WebViewRenderer $viewRenderer,
) {
}
public function __invoke(): ResponseInterface
{
$post = $this->posts->findById((int) $this->currentRoute->getArgument('id'));
return $this->viewRenderer->render(__DIR__ . '/template', [
'post' => $post,
]);
}
}The route is explicit:
php
use App\Web\Post\View;
use Yiisoft\Router\Route;
return [
Route::get('/post/{id:\d+}')
->action(View\Action::class)
->name('post/view'),
];Define routes before action bodies. It gives you a checklist of request flows you decided to keep in the Yii3 application.
See Actions, Middleware, and Routing and URL generation.
Requests, responses, redirects, sessions, and cookies
Yii 1.1 code often reads and writes through Yii::app():
php
$id = Yii::app()->request->getParam('id');
Yii::app()->user->setFlash('success', 'Saved.');
$this->redirect(['post/view', 'id' => $id]);Yii3 uses PSR-7 request and response objects and injectable services. Request input is available from ServerRequestInterface; redirects, sessions, cookies, and responses are explicit dependencies.
See Request, Response, Sessions, and Cookies.
Views and layouts
Yii 1.1 views use $this as the controller or widget context. Layouts and partials are rendered through controller methods:
php
<?php
/* @var $this PostController */
/* @var $post Post */
?>
<h1><?php echo CHtml::encode($post->title); ?></h1>In Yii3 templates, $this is a Yiisoft\View\WebView instance:
php
use Yiisoft\Html\Html;
use Yiisoft\View\WebView;
/**
* @var WebView $this
* @var Post $post
*/
$this->setTitle($post->getTitle());
?>
<h1><?= Html::encode($post->getTitle()) ?></h1>Move controller-specific view logic to the action or to a presenter. Pass only the data a template needs.
See View, View injections, and Template engines.
Assets and client scripts
Yii 1.1 applications often use CClientScript, themes, and extension-specific asset publishing:
php
Yii::app()->clientScript->registerCssFile('/css/site.css');
Yii::app()->clientScript->registerScriptFile('/js/site.js');Yii3 uses asset bundles from yiisoft/assets:
php
namespace App\Web\Shared\Layout\Main;
use Yiisoft\Assets\AssetBundle;
final class MainAsset extends AssetBundle
{
public ?string $basePath = '@assets/main';
public ?string $baseUrl = '@assetsUrl/main';
public ?string $sourcePath = '@assetsSource/main';
public array $css = ['site.css'];
public array $js = ['site.js'];
}Register bundles through the asset manager in layouts or views.
See Assets and Scripts, styles and metatags.
Models, forms, and validation
Yii 1.1 has CModel, CFormModel, and CActiveRecord. It is common for one model class to contain input data, labels, validation rules, database persistence, and business logic.
Yii3 works better when these responsibilities are split:
- form data and labels:
yiisoft/form-model; - validation rules:
yiisoft/validator; - form rendering:
yiisoft/form; - persistence: repositories,
yiisoft/db, oryiisoft/active-record; - business workflows: application services.
Yii 1.1 form model:
php
class ContactForm extends CFormModel
{
public $name;
public $email;
public function rules()
{
return [
['name, email', 'required'],
['email', 'email'],
];
}
}Yii3 form model:
php
use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Email;
use Yiisoft\Validator\Rule\Required;
final class ContactForm extends FormModel
{
#[Required]
public ?string $name = null;
#[Required]
#[Email]
public ?string $email = null;
}See Working with forms, Validating input, yiisoft/form, and yiisoft/form-model.
Active Record
Yii 1.1 Active Record uses CActiveRecord::model() and dynamic attributes:
php
class Post extends CActiveRecord
{
public static function model($className = __CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return '{{post}}';
}
public function rules()
{
return [
['title, body', 'required'],
['title', 'length', 'max' => 255],
];
}
}The incremental Yii3 option is yiisoft/active-record:
php
use Yiisoft\ActiveRecord\ActiveRecord;
final class Post extends ActiveRecord
{
protected int $id;
protected string $title;
protected string $body;
public function tableName(): string
{
return '{{%post}}';
}
public function getTitle(): ?string
{
return $this->title ?? null;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
}Move Yii 1.1 rules() to a form model, validator rules, or a service. Keep Active Record focused on persistence: table mapping, properties, relations, and database operations.
For more complex applications, consider replacing Active Record-heavy code with repositories and domain objects. That is more work initially, but it avoids carrying Yii 1.1 form and persistence coupling into the new application.
See Working with databases, Migrations, and yiisoft/active-record.
Database queries and migrations
Yii 1.1 code may use CDbCriteria, CDbCommand, CActiveDataProvider, and CGridView data providers. In Yii3, use yiisoft/db for database access and query building, and yiisoft/data / yiisoft/yii-dataview for data readers and widgets.
Migrations are not compatible across Yii 1.1 and Yii3. For an existing application, create a baseline from the current schema and use Yii3 migrations for new changes.
See Working with databases and Migrations.
GridView and data widgets
Yii 1.1 CGridView is often configured with CActiveDataProvider:
php
$this->widget('zii.widgets.grid.CGridView', [
'dataProvider' => $dataProvider,
'columns' => [
'id',
'title',
[
'class' => 'CButtonColumn',
],
],
]);Yii3 uses yiisoft/yii-dataview:
php
use Yiisoft\Data\Reader\ReadableDataInterface;
use Yiisoft\Yii\DataView\Column\DataColumn;
use Yiisoft\Yii\DataView\GridView\Column\ActionColumn;
use Yiisoft\Yii\DataView\GridView\GridView;
/**
* @var ReadableDataInterface $dataReader
*/
echo GridView::widget()
->dataReader($dataReader)
->columns(
new DataColumn('id'),
new DataColumn('title'),
new ActionColumn(),
);Replace the old data provider with a query service that returns a data reader. Model filters as validator rules and route or query parameters.
See Using htmx for partial reloads and yii-dataview GridView docs.
URL management
Yii 1.1:
php
echo CHtml::link('View', ['post/view', 'id' => $post->id]);
$url = Yii::app()->createUrl('post/view', ['id' => $post->id]);Yii3:
php
use Yiisoft\Html\Html;
use Yiisoft\Router\UrlGeneratorInterface;
$url = $urlGenerator->generate('post/view', ['id' => $post->getId()]);
echo Html::a('View', $url)->render();Prefer named routes. They make views and GridView action columns easier to design and maintain.
See Routing and URL generation.
Filters, access control, and authentication
Yii 1.1 controller filters are controller methods or filter classes:
php
public function filters()
{
return [
'accessControl',
];
}Yii3 uses middleware and focused packages. Move authentication, authorization, CSRF, locale detection, and request body parsing out of controllers and into middleware or services.
See Middleware, Authentication, and Authorization.
Widgets, helpers, and extensions
Yii 1.1 widgets and helpers usually depend on Yii-specific base classes such as CWidget, CHtml, and extension autoloading. In Yii3:
- replace helper calls with
yiisoft/html,yiisoft/arrays,yiisoft/strings, or application helpers; - replace extensions with Composer packages;
- rebuild custom widgets with
yiisoft/widgetor plain services/templates; - replace jQuery UI and Bootstrap widgets with current frontend packages or Yii3 widget packages where available.
See Widgets.
Console applications
Yii 1.1 console commands extend CConsoleCommand:
php
class ReportCommand extends CConsoleCommand
{
public function actionSend()
{
}
}Yii3 console commands are Symfony Console commands configured as services:
php
namespace App\Console;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'report/send')]
final class SendReportCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
return Command::SUCCESS;
}
}See Console applications.
Internationalization
Yii 1.1 message files under protected/messages can usually be kept conceptually, but configuration changes. Yii3 uses translator packages and explicit message sources. Move message files into the structure expected by the selected translator message package and inject translation services where needed.
See Internationalization.
What to refactor before reusing
These Yii 1.1 refactorings make the old behavior easier to understand and reuse:
- isolate database queries from controllers;
- remove direct
Yii::app()access from domain code; - move validation out of Active Record when it describes request input rather than database invariants;
- replace large controller actions with services;
- document all routes and URL rules;
- add tests around the flows you plan to rebuild first;
- upgrade PHP syntax gradually if the old runtime allows it.
The goal is not to make Yii 1.1 look like Yii3. The goal is to reduce hidden coupling before the code crosses the framework boundary.