Skip to content

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.

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, keep Yii::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, CActiveRecord methods, 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-autoload

When 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.1Yii3
protected/controllerssrc/Web/*/Action.php or controller-like services
protected/modelssrc/Domain, src/Shared, src/Db, or form models by responsibility
protected/viewstemplates near the feature, for example src/Web/HomePage/template.php
protected/views/layoutslayout templates and assets under src/Web/Shared/Layout
protected/componentsservices configured in the dependency injection container
protected/extensionsComposer packages or local packages
protected/commandssrc/Console/*Command.php
protected/messagestranslator message files
protected/configconfig/common, config/web, config/console, config/environments
webrootpublic

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.php and 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, or yiisoft/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/widget or plain services/templates;
  • replace jQuery UI and Bootstrap widgets with current frontend packages or Yii3 widget packages where available.

See Widgets.

控制台应用程序

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.