diff --git a/.travis.yml b/.travis.yml
index b7aea47..3f476f8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,8 +21,8 @@ before_script:
   - tests/unit/data/travis/cubrid-setup.sh
   - tests/unit/data/travis/sphinx-setup.sh
 
-#script:
-#  - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor
+script:
+  - phpunit --coverage-text --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor
 
 #after_script:
 #  - php vendor/bin/coveralls
diff --git a/README.md b/README.md
index 35a50f5..0ab29ca 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,22 @@
-Yii 2.0 Public Preview
-======================
+Yii PHP Framework Version 2
+===========================
 
-Thank you for choosing Yii - a high-performance component-based PHP framework.
+Thank you for choosing Yii 2 - a modern PHP framework designed for professional Web development.
 
-If you are looking for a production-ready PHP framework, please use
-[Yii v1.1](https://github.com/yiisoft/yii).
+Yii 2 is a complete rewrite of its previous version Yii 1.1 which is one of the most popular PHP frameworks.
+Yii 2 inherits the main spirit behind Yii for being simple, fast and highly extensible.
+Yii 2 requires PHP 5.4 and embraces best practices and protocols found in modern Web application development.
+
+
+**Yii 2 is not ready for production use yet.** We may make significant changes without prior notices.
+We expect to make the first stable release of Yii 2 in early 2014.
+
+If you mainly want to learn Yii with no real project development requirement, we highly recommend
+you start with Yii 2 as it will be our main focus for the next few years.
+
+If you have a real project with tight schedule, you should stick to [Yii 1.1](https://github.com/yiisoft/yii)
+which is the latest stable release of Yii.
 
-Yii 2.0 is still under heavy development. We may make significant changes
-without prior notices. **Yii 2.0 is not ready for production use yet.**
 
 [![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2/v/stable.png)](https://packagist.org/packages/yiisoft/yii2)
 [![Total Downloads](https://poser.pugx.org/yiisoft/yii2/downloads.png)](https://packagist.org/packages/yiisoft/yii2)
@@ -18,15 +27,14 @@ without prior notices. **Yii 2.0 is not ready for production use yet.**
 DIRECTORY STRUCTURE
 -------------------
 
-      apps/                ready-to-use Web apps built on Yii 2
-          advanced/        advanced app template with complex features
-          basic/           a simple app supporting user login and contact page
-          benchmark/       app demonstrating the minimal overhead introduced by the framework
+      apps/                ready-to-use application templates
+          advanced/        a template suitable for building sophisticated Web applications
+          basic/           a template suitable for building simple Web applications
+          benchmark/       an application demonstrating the performance of Yii
       build/               internally used build tools
       docs/                documentation
       extensions/          extensions
-      framework/           framework files
-          yii/             framework source files
+      framework/           core framework code
       tests/               tests of the core framework code
 
 
@@ -39,11 +47,11 @@ The minimum requirement by Yii is that your Web server supports PHP 5.4.
 DOCUMENTATION
 -------------
 
+A draft of the [Definitive Guide](docs/guide/index.md) is available.
+
 For 1.1 users, you may refer to [Upgrading from Yii 1.1](docs/guide/upgrade-from-v1.md)
 to have a general idea of what has changed in 2.0.
 
-[Definitive Guide draft](docs/guide/index.md) is available. It's not complete yet but main parts are already OK.
-
 
 HOW TO PARTICIPATE
 ------------------
diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json
index 9fd15d2..72268b4 100644
--- a/apps/advanced/composer.json
+++ b/apps/advanced/composer.json
@@ -15,11 +15,11 @@
 	"minimum-stability": "dev",
 	"require": {
 		"php": ">=5.4.0",
-		"yiisoft/yii2": "dev-master",
-		"yiisoft/yii2-swiftmailer": "dev-master",
-		"yiisoft/yii2-bootstrap": "dev-master",
-		"yiisoft/yii2-debug": "dev-master",
-		"yiisoft/yii2-gii": "dev-master"
+		"yiisoft/yii2": "*",
+		"yiisoft/yii2-swiftmailer": "*",
+		"yiisoft/yii2-bootstrap": "*",
+		"yiisoft/yii2-debug": "*",
+		"yiisoft/yii2-gii": "*"
 	},
 	"scripts": {
 		"post-create-project-cmd": [
diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php
index edb9cd9..db6dbe6 100644
--- a/apps/advanced/frontend/controllers/SiteController.php
+++ b/apps/advanced/frontend/controllers/SiteController.php
@@ -7,7 +7,7 @@ use yii\web\Controller;
 use common\models\LoginForm;
 use frontend\models\ContactForm;
 use common\models\User;
-use yii\web\HttpException;
+use yii\web\BadRequestHttpException;
 use yii\helpers\Security;
 
 class SiteController extends Controller
@@ -132,7 +132,7 @@ class SiteController extends Controller
 		]);
 
 		if (!$model) {
-			throw new HttpException(400, 'Wrong password reset token.');
+			throw new BadRequestHttpException('Wrong password reset token.');
 		}
 
 		$model->scenario = 'resetPassword';
diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php
index 47bdf37..84ae427 100644
--- a/apps/advanced/requirements.php
+++ b/apps/advanced/requirements.php
@@ -48,6 +48,13 @@ $requirements = [
 		'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>',
 		'memo' => 'Required for MySQL database.',
 	],
+	[
+		'name' => 'PDO PostgreSQL extension',
+		'mandatory' => false,
+		'condition' => extension_loaded('pdo_pgsql'),
+		'by' => 'All <a href="http://www.yiiframework.com/doc/api/#system.db">DB-related classes</a>',
+		'memo' => 'Required for PostgreSQL database.',
+	],
 	// Cache :
 	[
 		'name' => 'Memcache extension',
diff --git a/apps/basic/composer.json b/apps/basic/composer.json
index cef46b7..a794341 100644
--- a/apps/basic/composer.json
+++ b/apps/basic/composer.json
@@ -15,11 +15,11 @@
 	"minimum-stability": "dev",
 	"require": {
 		"php": ">=5.4.0",
-		"yiisoft/yii2": "dev-master",
-		"yiisoft/yii2-swiftmailer": "dev-master",
-		"yiisoft/yii2-bootstrap": "dev-master",
-		"yiisoft/yii2-debug": "dev-master",
-		"yiisoft/yii2-gii": "dev-master"
+		"yiisoft/yii2": "*",
+		"yiisoft/yii2-swiftmailer": "*",
+		"yiisoft/yii2-bootstrap": "*",
+		"yiisoft/yii2-debug": "*",
+		"yiisoft/yii2-gii": "*"
 	},
 	"scripts": {
 		"post-create-project-cmd": [
diff --git a/apps/benchmark/composer.json b/apps/benchmark/composer.json
index c074233..d980f9a 100644
--- a/apps/benchmark/composer.json
+++ b/apps/benchmark/composer.json
@@ -18,6 +18,6 @@
 	"minimum-stability": "dev",
 	"require": {
 		"php": ">=5.4.0",
-		"yiisoft/yii2": "dev-master"
+		"yiisoft/yii2": "*"
 	}
 }
diff --git a/build/build.xml b/build/build.xml
index b0975dc..846e4cf 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
 /**
- * Phing build file for Yii.
+ * Phing build file for Yii 2.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright 2008-2009 Yii Software LLC
+ * @copyright 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 -->
@@ -21,7 +21,7 @@
 
   <!-- these are required external commands -->
   <property name="php" value="php" /> <!-- PHP parser -->
-  <property name="hhc" value="hhc" /> <!-- compile phpdoc into CHM -->
+  <property name="composer" value="composer" /> <!-- composer -->
   <property name="pdflatex" value="pdflatex" />  <!-- generates PDF from LaTex -->
 
   <property name="pkgname" value="${phing.project.name}-${yii.version}.${yii.revision}"/>
diff --git a/composer.json b/composer.json
index 4cc1743..ad521cc 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
 {
 	"name": "yiisoft/yii2-dev",
-	"description": "Yii2 Web Programming Framework - Development Package",
+	"description": "Yii PHP Framework Version 2 - Development Package",
 	"keywords": ["yii", "framework"],
 	"homepage": "http://www.yiiframework.com/",
 	"type": "yii2-extension",
@@ -39,21 +39,6 @@
 			"name": "Paul Klimov",
 			"email": "klimov.paul@gmail.com",
 			"role": "Core framework development"
-		},
-		{
-			"name": "Wei Zhuo",
-			"email": "weizhuo@gmail.com",
-			"role": "Project site maintenance and development"
-		},
-		{
-			"name": "Sebastián Thierer",
-			"email": "sebas@artfos.com",
-			"role": "Component development"
-		},
-		{
-			"name": "Jeffrey Winesett",
-			"email": "jefftulsa@gmail.com",
-			"role": "Documentation and marketing"
 		}
 	],
 	"support": {
@@ -67,6 +52,7 @@
 	"replace": {
 		"yiisoft/yii2-bootstrap": "self.version",
 		"yiisoft/yii2-debug": "self.version",
+		"yiisoft/yii2-elasticsearch": "self.version",
 		"yiisoft/yii2-gii": "self.version",
 		"yiisoft/yii2-jui": "self.version",
 		"yiisoft/yii2-redis": "self.version",
@@ -94,6 +80,7 @@
 		"psr-0": {
 			"yii\\bootstrap\\": "extensions/bootstrap/",
 			"yii\\debug\\": "extensions/debug/",
+			"yii\\elasticsearch\\": "extensions/elasticsearch/",
 			"yii\\gii\\": "extensions/gii/",
 			"yii\\jui\\": "extensions/jui/",
 			"yii\\redis\\": "extensions/redis/",
diff --git a/docs/guide/apps-advanced.md b/docs/guide/apps-advanced.md
index 5090b00..3bdf754 100644
--- a/docs/guide/apps-advanced.md
+++ b/docs/guide/apps-advanced.md
@@ -139,11 +139,11 @@ directory:
 	"minimum-stability": "dev",
 	"require": {
 		"php": ">=5.4.0",
-		"yiisoft/yii2": "dev-master",
-		"yiisoft/yii2-swiftmailer": "dev-master",
-		"yiisoft/yii2-bootstrap": "dev-master",
-		"yiisoft/yii2-debug": "dev-master",
-		"yiisoft/yii2-gii": "dev-master"
+		"yiisoft/yii2": "*",
+		"yiisoft/yii2-swiftmailer": "*",
+		"yiisoft/yii2-bootstrap": "*",
+		"yiisoft/yii2-debug": "*",
+		"yiisoft/yii2-gii": "*"
 	},
 	"scripts": {
 		"post-create-project-cmd": [
diff --git a/docs/guide/apps-basic.md b/docs/guide/apps-basic.md
index a6c5ed1..0125070 100644
--- a/docs/guide/apps-basic.md
+++ b/docs/guide/apps-basic.md
@@ -130,11 +130,11 @@ directory:
 	"minimum-stability": "dev",
 	"require": {
 		"php": ">=5.4.0",
-		"yiisoft/yii2": "dev-master",
-		"yiisoft/yii2-swiftmailer": "dev-master",
-		"yiisoft/yii2-bootstrap": "dev-master",
-		"yiisoft/yii2-debug": "dev-master",
-		"yiisoft/yii2-gii": "dev-master"
+		"yiisoft/yii2": "*",
+		"yiisoft/yii2-swiftmailer": "*",
+		"yiisoft/yii2-bootstrap": "*",
+		"yiisoft/yii2-debug": "*",
+		"yiisoft/yii2-gii": "*"
 	},
 	"scripts": {
 		"post-create-project-cmd": [
diff --git a/docs/guide/assets.md b/docs/guide/assets.md
index 44dc29f..da3c8b3 100644
--- a/docs/guide/assets.md
+++ b/docs/guide/assets.md
@@ -114,4 +114,157 @@ return [
 ```
 
 There are two main benefits in enabling it. First it is faster since no copying is required and second is that assets
-will always be up to date with source files.
\ No newline at end of file
+will always be up to date with source files.
+
+Compressing and combining assets
+--------------------------------
+
+To improve application performance you can compress and then combine several CSS or JS files into lesser number of files
+therefore reducing number of HTTP requests and overall download size needed to load a web page.  Yii provides a console
+command that allows you to do both.
+
+### Preparing configuration
+
+In order to use `asset` command you should prepare a configuration first. A template for it can be generated using
+
+```
+yii asset/template /path/to/myapp/config.php
+```
+
+The template itself looks like the following:
+
+```php
+<?php
+/**
+ * Configuration file for the "yii asset" console command.
+ * Note that in the console environment, some path aliases like '@webroot' and '@web' may not exist.
+ * Please define these missing path aliases.
+ */
+return [
+	// The list of asset bundles to compress:
+	'bundles' => [
+		// 'yii\web\YiiAsset',
+		// 'yii\web\JqueryAsset',
+	],
+	// Asset bundle for compression output:
+	'targets' => [
+		'app\config\AllAsset' => [
+			'basePath' => 'path/to/web',
+			'baseUrl' => '',
+			'js' => 'js/all-{ts}.js',
+			'css' => 'css/all-{ts}.css',
+		],
+	],
+	// Asset manager configuration:
+	'assetManager' => [
+		'basePath' => __DIR__,
+		'baseUrl' => '',
+	],
+];
+```
+
+In the above keys are `properties` of `AssetController`. `bundles` list contains bundles that should be compressed. These are typically what's used by application.
+`targets` contains a list of bundles that define how resulting files will be written. In our case we're writing
+everything to `path/to/web` that can be accessed like `http://example.com/` i.e. it is website root directory.
+
+> Note: in the console environment some path aliases like '@webroot' and '@web' may not exist,
+  so corresponding paths inside the configuration should be specified directly.
+
+JavaScript files are combined, compressed and written to `js/all-{ts}.js` where {ts} is replaced with current UNIX
+timestamp.
+
+### Providing compression tools
+
+The command relies on external compression tools that are not bundled with Yii so you need to provide CSS and JS
+compressors which are correspondingly specified via `cssCompressor` and `jsCompression` properties. If compressor is
+specified as a string it is treated as a shell command template which should contain two placeholders: `{from}` that
+is replaced by source file name and `{to}` that is replaced by output file name. Another way to specify compressor is
+to use any valid PHP callback.
+
+By default for JavaScript compression Yii tries to use
+[Google Closure compiler](https://developers.google.com/closure/compiler/) that is expected to be in a file named
+`compiler.jar`.
+
+For CSS compression Yii assumes that [YUI Compressor](https://github.com/yui/yuicompressor/) is looked up in a file
+named `yuicompressor.jar`.
+
+In order to compress resources with these two you need to download both and place where your `yii` console bootstrap
+file is using named mentioned above. Since both are Java tools you need JRE installed.
+
+### Performing compression
+
+After configuration is adjusted you can run the `compress` action, using created config:
+
+```
+yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php
+```
+
+Now processing takes some time and finally finished. You need to adjust your web application config to use compressed
+assets file like the following:
+
+```php
+'components' => [
+	// ...
+	'assetManager' => [
+		'bundles' => require /path/to/myapp/config/assets_compressed.php,
+	],
+],
+```
+
+Using asset converter
+---------------------
+
+Instead of using CSS and JavaScript directly often developers are using their improved versions such as LESS or SCSS
+for CSS or Microsoft TypeScript for JavaScript. Using these with Yii is easy.
+
+First of all, corresponding compression tools should be installed and should be availabe from where `yii` console
+bootstrap file is. The following lists file extensions and their corresponding conversion tool names that Yii converter
+recognizes:
+
+- LESS: `less` - `lessc`
+- SCSS: `scss`, `sass` - `sass`
+- Stylus: `styl` - `stylus`
+- CoffeeScript: `coffee` - `coffee`
+- TypeScript: `ts` - `tsc`
+
+So if the corresponding tool is installed you can specify any of these in asset bundle:
+
+```php
+class AppAsset extends AssetBundle
+{
+	public $basePath = '@webroot';
+	public $baseUrl = '@web';
+	public $css = [
+		'css/site.less',
+	];
+	public $js = [
+		'js/site.ts',
+	];
+	public $depends = [
+		'yii\web\YiiAsset',
+		'yii\bootstrap\BootstrapAsset',
+	];
+}
+```
+
+In order to adjust conversion tool call parameters or add new ones you can use application config:
+
+```php
+// ...
+'components' => [
+	'assetManager' => [
+		'converter' => [
+			'class' => 'yii\web\AssetConverter',
+			'commands' => [
+				'less' => ['css', 'lessc {from} {to} --no-color'],
+				'ts' => ['js', 'tsc --out {to} {from}'],
+			],
+		],
+	],
+],
+```
+
+In the above we've left two types of extra file extensions. First one is `less` that can be specified in `css` part
+of an asset bundle. Conversion is performed via running `lessc {from} {to} --no-color` where `{from}` is replaced with
+LESS file path while `{to}` is replaced with target CSS file path. Second one is `ts` that can be specified in `js` part
+of an asset bundle. The command that is run during conversion is in the same format that is used for `less`.
\ No newline at end of file
diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md
index 8f2b97f..086aa32 100644
--- a/docs/guide/authorization.md
+++ b/docs/guide/authorization.md
@@ -234,10 +234,10 @@ public function editArticle($id)
 {
   $article = Article::find($id);
   if (!$article) {
-    throw new HttpException(404);
+    throw new NotFoundHttpException;
   }
   if (!\Yii::$app->user->checkAccess('edit_article', ['article' => $article])) {
-    throw new HttpException(403);
+    throw new AccessDeniedHttpException;
   }
   // ...
 }
@@ -250,7 +250,7 @@ public function editArticle($id)
 {
     $article = Article::find(['id' => $id, 'author_id' => \Yii::$app->user->id]);
     if (!$article) {
-      throw new HttpException(404);
+      throw new NotFoundHttpException;
     }
     // ...
 }
diff --git a/docs/guide/behaviors.md b/docs/guide/behaviors.md
index 8a67270..b425fd3 100644
--- a/docs/guide/behaviors.md
+++ b/docs/guide/behaviors.md
@@ -1,4 +1,41 @@
 Behaviors
 =========
 
-TDB
\ No newline at end of file
+A behavior (also knows as mixin) can be used to enhance the functionality of an existing component without modifying its
+code. In particular, it can "inject" its own methods and properties into the component and make them directly accessible
+via the component. It can also respond to the events triggered in the component and thus intercept the normal
+code execution. Unlike PHP traits, behaviors could be attached to classes at runtime.
+
+Using behaviors
+---------------
+
+Behavior can be attached to any class that extends from `Component`. In order to do so you need to implement `behaviors`
+method. Yii provides `AutoTimestamp` behavior that handles updating timestamp fields on saving active record model.
+
+```php
+class User extends ActiveRecord
+{
+	// ...
+
+	public function behaviors()
+	{
+		return [
+			'timestamp' => [
+				'class' => 'yii\behaviors\AutoTimestamp',
+				'attributes' => [
+					ActiveRecord::EVENT_BEFORE_INSERT => ['create_time', 'update_time'],
+					ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time',
+				],
+			],
+		];
+	}
+}
+```
+
+In the above `class` value is a string containing fully qualified behavior class name. All the other key-values are
+assigned to corresponding properties of the class.
+
+Creating your own behaviors
+---------------------------
+
+TBD
\ No newline at end of file
diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md
index 11f2470..ab4e410 100644
--- a/docs/guide/configuration.md
+++ b/docs/guide/configuration.md
@@ -96,4 +96,18 @@ not be instantiated and configured at all.
 Setting component defaults classwide
 ------------------------------------
 
-TBD
+For each component you can specifiy classwide defaults. For example, if we want to change class for all `LinkPager`
+widgets without specifying it over and over again when widget is called we can do it like the following:
+
+```php
+\Yii::$objectConfig = [
+	'yii\widgets\LinkPager' => [
+		'options' => [
+			'class' => 'pagination',
+		],
+	],
+];
+```
+
+The code above should be executed once before `LinkPager` widget is used. It can be done in `index.php`, application
+config or anywhere else.
\ No newline at end of file
diff --git a/docs/guide/console.md b/docs/guide/console.md
index 055c206..9bc6069 100644
--- a/docs/guide/console.md
+++ b/docs/guide/console.md
@@ -1,4 +1,83 @@
-Building console applications
-=============================
+Console applications
+====================
 
-TDB
\ No newline at end of file
+Yii has full featured support of console. Console application structure in Yii is very similar to web application. It
+consists of one or more [[\yii\console\Controller]] (often referred to as commands). Each has one or more actions.
+
+Usage
+-----
+
+You can execute controller action using the following syntax:
+
+```
+yii <route> [--param1=value1 --param2 ...]
+```
+
+For example, `MigrationController::create` with `MigrationController::$migrationTable` set can be called from command
+line like the following:
+
+```
+yii migreate/create --migrationTable=my_migration
+```
+
+In the above `yii` is console application entry script described below.
+
+Entry script
+------------
+
+Console application entry script is typically called `yii`, located in your application root directory and contains
+code like the following:
+
+```php
+#!/usr/bin/env php
+<?php
+/**
+ * Yii console bootstrap file.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+defined('YII_DEBUG') or define('YII_DEBUG', true);
+
+// fcgi doesn't have STDIN defined by default
+defined('STDIN') or define('STDIN', fopen('php://stdin', 'r'));
+
+require(__DIR__ . '/vendor/autoload.php');
+require(__DIR__ . '/vendor/yiisoft/yii2/yii/Yii.php');
+
+$config = require(__DIR__ . '/config/console.php');
+
+$application = new yii\console\Application($config);
+$exitCode = $application->run();
+exit($exitCode);
+
+```
+
+This script is a part of your application so you're free to adjust it. There `YII_DEBUG` can be turned off if you do
+not want to see stacktrace on error and want to improve overall performance. In both basic and advanced application
+templates it is enabled to provide more developer-friendly environment.
+
+Configuration
+-------------
+
+As can be seen in the code above, console application uses its own config files named `console.php` so you need to
+configure database connection and the rest of the components you're going to use there in that file. If web and console
+application configs have a lot in common it's a good idea to move matching parts into their own config files as it was
+done in advanced application template.
+
+
+Creating your own console commands
+----------------------------------
+
+### Controller
+
+### Action
+
+### Parameters
+
+### Return codes
+
+Using return codes is the best practice of console application development. If command returns `0` it means everything
+is OK. If it is a number more than zero, we have an error and integer returned is the error code.
\ No newline at end of file
diff --git a/docs/guide/controller.md b/docs/guide/controller.md
index 571a833..97ffe49 100644
--- a/docs/guide/controller.md
+++ b/docs/guide/controller.md
@@ -122,7 +122,7 @@ class BlogController extends Controller
 	{
 		$post = Post::find($id);
 		if (!$post) {
-			throw new HttpException(404);
+			throw new NotFoundHttpException;
 		}
 
 		if (\Yii::$app->request->isPost)) {
@@ -183,7 +183,7 @@ Action Filters
 
 Action filters are implemented via behaviors. You should extend from `ActionFilter` to
 define a new filter. To use a filter, you should attach the filter class to the controller
-as a behavior. For example, to use the `AccessControl` filter, you should have the following
+as a behavior. For example, to use the [[AccessControl]] filter, you should have the following
 code in a controller:
 
 ```php
@@ -200,7 +200,8 @@ public function behaviors()
 }
 ```
 
-more TDB
+In order to learn more about access control check [authorization](authorization.md) section of the guide.
+Two other filters, [[PageCache]] and [[HttpCache]] are described in [caching](caching.md) section of the guide.
 
 Catching all incoming requests
 ------------------------------
diff --git a/docs/guide/database-basics.md b/docs/guide/database-basics.md
index 511ecaf..520da22 100644
--- a/docs/guide/database-basics.md
+++ b/docs/guide/database-basics.md
@@ -9,8 +9,8 @@ uniform API and solves some inconsistencies between different DBMS. By default Y
 - [SQLite](http://sqlite.org/)
 - [PostgreSQL](http://www.postgresql.org/)
 - [CUBRID](http://www.cubrid.org/) (version 9.1.0 and higher).
-- Oracle
-- MSSQL
+- [Oracle](http://www.oracle.com/us/products/database/overview/index.html)
+- [MSSQL](https://www.microsoft.com/en-us/sqlserver/default.aspx)
 
 
 Configuration
@@ -42,6 +42,7 @@ return [
 	// ...
 ];
 ```
+
 Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) for more details
 on the format of the DSN string.
 
diff --git a/docs/guide/debugger.md b/docs/guide/debugger.md
index 9006d9d..a49471a 100644
--- a/docs/guide/debugger.md
+++ b/docs/guide/debugger.md
@@ -7,19 +7,21 @@ about currently opened page while using debugger you can analyze data collected 
 Installing and configuring
 --------------------------
 
-How to use it
--------------
-
 Add these lines to your config file:
 
 ```
-    'preload' => ['debug'],
-    'modules' => [
-            'debug' => ['yii\debug\Module']
-        ]
+'preload' => ['debug'],
+'modules' => [
+	'debug' => ['yii\debug\Module']
+]
 ```
 
-**Watch out: by default the debug module only works when browsing the website from the localhost. If you want to use it on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP.**
+**Watch out: by default the debug module only works when browsing the website from the localhost. If you want to use it
+on a remote (staging) server, add the parameter allowedIPs to the config to whitelist your IP.**
+
+How to use it
+-------------
+
 
 Creating your own panels
 ------------------------
diff --git a/docs/guide/gii.md b/docs/guide/gii.md
index 54f6a36..525c567 100644
--- a/docs/guide/gii.md
+++ b/docs/guide/gii.md
@@ -7,17 +7,17 @@ as well as complete CRUD controllers.
 Installing and configuring
 --------------------------
 
-How to use it
--------------
-
 Add these lines to your config file:
 
 ```php
-    'modules' => [
-            'gii' => ['yii\gii\Module']
-        ]
+'modules' => [
+	'gii' => ['yii\gii\Module']
+]
 ```
 
+How to use it
+-------------
+
 Creating your own templates
 ---------------------------
 
diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md
index 477de0a..482b318 100644
--- a/docs/guide/i18n.md
+++ b/docs/guide/i18n.md
@@ -47,7 +47,7 @@ translation of the message from source language into target language. Message it
 echo \Yii::t('app', 'This is a string to translate!');
 ```
 
-Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration:
+Yii tries to load appropriate translation from one of the message sources defined via `i18n` component configuration:
 
 ```php
 'components' => [
@@ -239,7 +239,7 @@ you'll get "Inconsistent types declared for an argument: U_ARGUMENT_TYPE_MISMATC
 Total {count, number} {count, plural, one{item} other{items}}.
 ```
 
-To learn which inflection forms you should specify for your language you can referer to
+To learn which inflection forms you should specify for your language you can referrer to
 [rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html).
 
 ### Selections
@@ -264,3 +264,5 @@ Formatters
 
 In order to use formatters you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP
 extension.
+
+TBD: provided classes overview.
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 422ca64..a560a3c 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -47,7 +47,7 @@ Extensions and 3rd party libraries
 
 - [Composer](composer.md) - How to manage applications dependencies via composer
 - [Extending Yii](extensions.md)
-- [Template engines](template.md) - Using template engines such as Smary or Twig
+- [Template engines](template.md) - Using template engines such as Smarty or Twig
 
 Security and access control
 ===========================
diff --git a/docs/guide/performance.md b/docs/guide/performance.md
index 42aa042..9309c2c 100644
--- a/docs/guide/performance.md
+++ b/docs/guide/performance.md
@@ -28,9 +28,10 @@ logger may record additional debug information for every message being logged.
 ### Enabling PHP opcode cache
 
 Enabling the PHP opcode cache improves any PHP application performance and lowers
-memory usage significantly. Yii is no exception. It was tested with
-[APC PHP extension](http://php.net/manual/en/book.apc.php) that caches
-and optimizes PHP intermediate code and avoids the time spent in parsing PHP
+memory usage significantly. Yii is no exception. It was tested with both
+[PHP 5.5 OPcache](http://php.net/manual/en/book.opcache.php) and
+[APC PHP extension](http://php.net/manual/en/book.apc.php). Both cache
+and optimize PHP intermediate code and avoid the time spent in parsing PHP
 scripts for every incoming request.
 
 ### Turning on ActiveRecord database schema caching
@@ -69,7 +70,10 @@ Note that `cache` application component should be configured.
 
 ### Combining and Minimizing Assets
 
-TBD
+It is possible to combine and minimize assets, typically JavaScript and CSS, in order to slightly improve page load
+time and therefore deliver better experience for end user of your application.
+
+In order to learn how it can be achieved, refer to [assets](assets.md) guide section.
 
 ### Using better storage for sessions
 
@@ -118,7 +122,38 @@ save the rendering cost for the whole page.
 
 ### Leveraging HTTP to save processing time and bandwidth
 
-TBD
+Leveraging HTTP caching saves both processing time, bandwidth and resources significantly. It can be implemented by
+sending either `ETag` or `Last-Modified` header in your application response. If browser is implemented according to
+HTTP specification (most browsers are), content will be fetched only if it is different from what it was prevously.
+
+Forming proper headers is time consuming task so Yii provides a shortcut in form of controller filter
+[[\yii\web\HttpCache]]. Using it is very easy. In a controller you need to implement `behaviors` method like
+the following:
+
+```php
+public function behaviors()
+{
+	return [
+		'httpCache' => [
+			'class' => \yii\web\HttpCache::className(),
+			'only' => ['list'],
+			'lastModified' => function ($action, $params) {
+				$q = new Query();
+				return strtotime($q->from('users')->max('updated_timestamp'));
+			},
+			// 'etagSeed' => function ($action, $params) {
+				// return // generate etag seed here
+			//}
+		],
+	];
+}
+```
+
+In the code above one can use either `etagSeed` or `lastModified`. Implementing both isn't necessary. The goal is to
+determine if content was modified in a way that is cheaper than fetching and rendering that content. `lastModified`
+should return unix timestamp of the last content modification while `etagSeed` should return a string that is then
+used to generate `ETag` header value.
+
 
 ### Database Optimization
 
@@ -140,7 +175,7 @@ to create one or several objects to represent each row of query result. For data
 intensive applications, using DAO or database APIs at lower level could be
 a better choice.
 
-Last but not least, use LIMIT in your SELECT queries. This avoids fetching
+Last but not least, use `LIMIT` in your `SELECT` queries. This avoids fetching
 overwhelming data from database and exhausting the memory allocated to PHP.
 
 ### Using asArray
diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md
index c02a48d..d644190 100644
--- a/docs/guide/upgrade-from-v1.md
+++ b/docs/guide/upgrade-from-v1.md
@@ -518,5 +518,8 @@ TBD
 Integration with Composer
 -------------------------
 
-TBD
+Yii is fully inegrated with the package manager for PHP named Composer that resolves dependencies, keeps your code
+up to date updating it semi-automatically and manages autoloading for third party libraries no matter which autoloading
+these are using.
 
+In order to learn more refer to [composer](composer.md) and [installation](installation.md) sections of the guide.
\ No newline at end of file
diff --git a/docs/guide/url.md b/docs/guide/url.md
index 454a827..e54be4b 100644
--- a/docs/guide/url.md
+++ b/docs/guide/url.md
@@ -92,6 +92,8 @@ return [
 
 ### Handling REST
 
+TBD: [[\yii\web\VerbFiler]]
+
 
 URL parsing
 -----------
@@ -118,3 +120,72 @@ return [
 
 Creating your own rule classes
 ------------------------------
+
+[[\yii\web\UrlRule]] class is used for both parsing URL into parameters and creating URL based on parameters. Despite
+the fact that default implementation is flexible enough for majority of projects, there could be a situation when using
+your own rule class is the best choice. For example, in a car dealer website, we may want to support the URL format like
+`/Manufacturer/Model`, where `Manufacturer` and `Model` must both match some data in a database table. The default rule
+class will not work because it mostly relies on statically declared regular expressions which have no database knowledge.
+
+We can write a new URL rule class by extending from [[\yii\web\UrlRule]] and use it in one or multiple URL rules. Using
+the above car dealer website as an example, we may declare the following URL rules in application config:
+
+```php
+// ...
+'components' => [
+	'urlManager' => [
+		'rules' => [
+			'<action:(login|logout|about)>' => 'site/<action>',
+
+			// ...
+
+			['class' => 'app\components\CarUrlRule', 'connectionID' => 'db', ...],
+		],
+	],
+],
+```
+
+In the above, we use the custom URL rule class `CarUrlRule` to handle
+the URL format `/Manufacturer/Model`. The class can be written like the following:
+
+```php
+namespace \app\components;
+
+use \yii\web\UrlRule;
+
+class CarUrlRule extends UrlRule
+{
+	public $connectionID = 'db';
+
+	public function createUrl($manager, $route, $params)
+	{
+		if ($route === 'car/index') {
+			if (isset($params['manufacturer'], $params['model'])) {
+				return $params['manufacturer'] . '/' . $params['model'];
+			} elseif (isset($params['manufacturer'])) {
+				return $params['manufacturer'];
+			}
+		}
+		return false;  // this rule does not apply
+	}
+
+	public function parseRequest($manager, $request)
+	{
+		$pathInfo = $request->getPathInfo();
+		if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
+			// check $matches[1] and $matches[3] to see
+			// if they match a manufacturer and a model in the database
+			// If so, set $_GET['manufacturer'] and/or $_GET['model']
+			// and return 'car/index'
+		}
+		return false;  // this rule does not apply
+	}
+}
+```
+
+Besides the above usage, custom URL rule classes can also be implemented
+for many other purposes. For example, we can write a rule class to log the URL parsing
+and creation requests. This may be useful during development stage. We can also
+write a rule class to display a special 404 error page in case all other URL rules fail
+to resolve the current request. Note that in this case, the rule of this special class
+must be declared as the last rule.
diff --git a/docs/guide/view.md b/docs/guide/view.md
index 7161a5a..fdfc061 100644
--- a/docs/guide/view.md
+++ b/docs/guide/view.md
@@ -277,7 +277,12 @@ use yii\helpers\Html;
 In the markup above there's some code. First of all, `$content` is a variable that will contain result of views rendered
 with controller's `$this->render()` method.
 
-TBD
+We are importing `Html` helper via standard PHP `use` statement. This helper is typically used for almost all views
+where one need to escape outputted data.
+
+Several special methods such as `beginPage`/`endPage`, `head`, `beginBody`/`endBody` are triggering page rendering events
+that are used for registering scripts, links and process page in many other ways. Always include these in your layout in
+order for rendering to work correctly.
 
 ### Partials
 
diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php
index 164392e..d8d799f 100644
--- a/extensions/composer/Installer.php
+++ b/extensions/composer/Installer.php
@@ -26,7 +26,7 @@ class Installer extends LibraryInstaller
 	const EXTENSION_FILE = 'yiisoft/extensions.php';
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function supports($packageType)
 	{
@@ -34,7 +34,7 @@ class Installer extends LibraryInstaller
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
 	{
@@ -49,7 +49,7 @@ class Installer extends LibraryInstaller
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
 	{
@@ -63,7 +63,7 @@ class Installer extends LibraryInstaller
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
 	{
diff --git a/extensions/composer/Plugin.php b/extensions/composer/Plugin.php
index 40bd8e5..1111738 100644
--- a/extensions/composer/Plugin.php
+++ b/extensions/composer/Plugin.php
@@ -20,7 +20,7 @@ use Composer\Plugin\PluginInterface;
 class Plugin implements PluginInterface
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function activate(Composer $composer, IOInterface $io)
 	{
diff --git a/extensions/debug/Module.php b/extensions/debug/Module.php
index 51f69a6..8a20021 100644
--- a/extensions/debug/Module.php
+++ b/extensions/debug/Module.php
@@ -10,7 +10,7 @@ namespace yii\debug;
 use Yii;
 use yii\base\Application;
 use yii\web\View;
-use yii\web\HttpException;
+use yii\web\AccessDeniedHttpException;
 
 /**
  * The Yii Debug Module provides the debug toolbar and debugger
@@ -79,7 +79,7 @@ class Module extends \yii\base\Module
 		} elseif ($action->id === 'toolbar') {
 			return false;
 		} else {
-			throw new HttpException(403, 'You are not allowed to access this page.');
+			throw new AccessDeniedHttpException('You are not allowed to access this page.');
 		}
 	}
 
diff --git a/extensions/debug/controllers/DefaultController.php b/extensions/debug/controllers/DefaultController.php
index a4ac633..6694d26 100644
--- a/extensions/debug/controllers/DefaultController.php
+++ b/extensions/debug/controllers/DefaultController.php
@@ -9,7 +9,7 @@ namespace yii\debug\controllers;
 
 use Yii;
 use yii\web\Controller;
-use yii\web\HttpException;
+use yii\web\NotFoundHttpException;
 
 /**
  * @author Qiang Xue <qiang.xue@gmail.com>
@@ -99,7 +99,7 @@ class DefaultController extends Controller
 			}
 			$this->summary = $data['summary'];
 		} else {
-			throw new HttpException(404, "Unable to find debug data tagged with '$tag'.");
+			throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
 		}
 	}
 }
diff --git a/extensions/elasticsearch/ActiveQuery.php b/extensions/elasticsearch/ActiveQuery.php
index b444f05..96d6681 100644
--- a/extensions/elasticsearch/ActiveQuery.php
+++ b/extensions/elasticsearch/ActiveQuery.php
@@ -139,7 +139,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function search($db = null, $options = [])
 	{
@@ -161,7 +161,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function scalar($field, $db = null)
 	{
@@ -177,7 +177,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function column($field, $db = null)
 	{
diff --git a/extensions/elasticsearch/ActiveRecord.php b/extensions/elasticsearch/ActiveRecord.php
index efdb5fe..d6ada20 100644
--- a/extensions/elasticsearch/ActiveRecord.php
+++ b/extensions/elasticsearch/ActiveRecord.php
@@ -10,6 +10,8 @@ namespace yii\elasticsearch;
 use yii\base\InvalidCallException;
 use yii\base\InvalidConfigException;
 use yii\base\NotSupportedException;
+use yii\db\ActiveRecordInterface;
+use yii\db\BaseActiveRecord;
 use yii\helpers\Inflector;
 use yii\helpers\Json;
 use yii\helpers\StringHelper;
@@ -42,7 +44,7 @@ use yii\helpers\StringHelper;
  * @author Carsten Brandt <mail@cebe.cc>
  * @since 2.0
  */
-class ActiveRecord extends \yii\db\ActiveRecord
+class ActiveRecord extends BaseActiveRecord
 {
 	const PRIMARY_KEY_NAME = 'id';
 
@@ -61,7 +63,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public static function find($q = null)
 	{
@@ -138,7 +140,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	// TODO add percolate functionality http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-percolate.html
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public static function createQuery()
 	{
@@ -146,7 +148,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public static function createActiveRelation($config = [])
 	{
@@ -175,7 +177,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getPrimaryKey($asArray = false)
 	{
@@ -187,7 +189,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getOldPrimaryKey($asArray = false)
 	{
@@ -428,47 +430,4 @@ class ActiveRecord extends \yii\db\ActiveRecord
 		}
 		return $n;
 	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public static function updateAllCounters($counters, $condition = null, $params = [])
-	{
-		throw new NotSupportedException('Update Counters is not supported by elasticsearch ActiveRecord.');
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public static function getTableSchema()
-	{
-		throw new NotSupportedException('getTableSchema() is not supported by elasticsearch ActiveRecord.');
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public static function tableName()
-	{
-		return static::index() . '/' . static::type();
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public static function findBySql($sql, $params = [])
-	{
-		throw new NotSupportedException('findBySql() is not supported by elasticsearch ActiveRecord.');
-	}
-
-	/**
-	 * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
-	 * This method will always return false as transactional operations are not supported by elasticsearch.
-	 * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
-	 * @return boolean whether the specified operation is transactional in the current [[scenario]].
-	 */
-	public function isTransactional($operation)
-	{
-		return false;
-	}
 }
diff --git a/extensions/elasticsearch/Connection.php b/extensions/elasticsearch/Connection.php
index d5275e8..098d6ee 100644
--- a/extensions/elasticsearch/Connection.php
+++ b/extensions/elasticsearch/Connection.php
@@ -15,6 +15,9 @@ use yii\helpers\Json;
 /**
  * elasticsearch Connection is used to connect to an elasticsearch cluster version 0.20 or higher
  *
+ * @property string $driverName Name of the DB driver. This property is read-only.
+ * @property boolean $isActive Whether the DB connection is established. This property is read-only.
+ *
  * @author Carsten Brandt <mail@cebe.cc>
  * @since 2.0
  */
@@ -264,7 +267,7 @@ class Connection extends Component
 			if (strncmp($host, 'inet[/', 6) == 0) {
 				$host = substr($host, 6, -1);
 			}
-			$profile = $q . $requestBody;
+			$profile = $method . ' ' . $q . '#' . $requestBody;
 			$url = 'http://' . $host . '/' . $q;
 		} else {
 			$profile = false;
diff --git a/extensions/elasticsearch/DebugPanel.php b/extensions/elasticsearch/DebugPanel.php
new file mode 100644
index 0000000..da5a824
--- /dev/null
+++ b/extensions/elasticsearch/DebugPanel.php
@@ -0,0 +1,168 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\elasticsearch;
+
+use yii\debug\Panel;
+use yii\log\Logger;
+use yii\helpers\Html;
+use yii\web\View;
+
+/**
+ * Debugger panel that collects and displays elasticsearch queries performed.
+ *
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @since 2.0
+ */
+class DebugPanel extends Panel
+{
+	public function getName()
+	{
+		return 'Elasticsearch';
+	}
+
+	public function getSummary()
+	{
+		$timings = $this->calculateTimings();
+		$queryCount = count($timings);
+		$queryTime = 0;
+		foreach ($timings as $timing) {
+			$queryTime += $timing[3];
+		}
+		$queryTime = number_format($queryTime * 1000) . ' ms';
+		$url = $this->getUrl();
+		$output = <<<EOD
+<div class="yii-debug-toolbar-block">
+	<a href="$url" title="Executed $queryCount elasticsearch queries which took $queryTime.">
+		ES <span class="label">$queryCount</span> <span class="label">$queryTime</span>
+	</a>
+</div>
+EOD;
+		return $queryCount > 0 ? $output : '';
+	}
+
+	public function getDetail()
+	{
+		$rows = [];
+		$i = 0;
+		foreach ($this->data['messages'] as $log) {
+			list ($message, $level, $category, $time, $traces) = $log;
+			if ($level == Logger::LEVEL_PROFILE_BEGIN) {
+				continue;
+			}
+			if (($pos = mb_strpos($message, "#")) !== false) {
+				$url = mb_substr($message, 0, $pos);
+				$body = mb_substr($message, $pos + 1);
+			} else {
+				$url = $message;
+				$body = null;
+			}
+			$traceString = '';
+			if (!empty($traces)) {
+				$traceString .= Html::ul($traces, [
+					'class' => 'trace',
+					'item' => function ($trace) {
+							return "<li>{$trace['file']}({$trace['line']})</li>";
+						},
+				]);
+			}
+			$runLinks = '';
+			$c = 0;
+			\Yii::$app->elasticsearch->open();
+			foreach(\Yii::$app->elasticsearch->nodes as $node) {
+				$pos = mb_strpos($url, ' ');
+				$type = mb_substr($url, 0, $pos);
+				if ($type == 'GET' && !empty($body)) {
+					$type = 'POST';
+				}
+				$host = $node['http_address'];
+				if (strncmp($host, 'inet[/', 6) == 0) {
+					$host = substr($host, 6, -1);
+				}
+				$nodeUrl = 'http://' . $host . '/' . mb_substr($url, $pos + 1);
+				$nodeUrl .= (strpos($nodeUrl, '?') === false) ? '?pretty=true' : '&pretty=true';
+				$nodeBody = json_encode($body);
+				\Yii::$app->view->registerJs(<<<JS
+$('#elastic-link-$i-$c').on('click', function() {
+	$('#elastic-result-$i').html('Sending $type request to $nodeUrl...');
+	$('#elastic-result-$i').parent('tr').show();
+	$.ajax({
+		type: "$type",
+		url: "$nodeUrl",
+		body: $nodeBody,
+		success: function( data ) {
+			$('#elastic-result-$i').html(data);
+		},
+		dataType: "text"
+	});
+
+	return false;
+});
+JS
+, View::POS_READY);
+				$runLinks .= Html::a(isset($node['name']) ? $node['name'] : $node['http_address'], '#', ['id' => "elastic-link-$i-$c"]) . '<br/>';
+				$c++;
+			}
+			$rows[] = "<tr><td style=\"width: 80%;\"><div><b>$url</b><br/><p>$body</p>$traceString</div></td><td style=\"width: 20%;\">$runLinks</td></tr><tr style=\"display: none;\"><td colspan=\"2\" id=\"elastic-result-$i\"></td></tr>";
+			$i++;
+		}
+		$rows = implode("\n", $rows);
+		return <<<HTML
+<h1>Elasticsearch Queries</h1>
+
+<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
+<thead>
+<tr>
+	<th style="width: 80%;">Url / Query</th>
+	<th style="width: 20%;">Run Query on node</th>
+</tr>
+</thead>
+<tbody>
+$rows
+</tbody>
+</table>
+HTML;
+	}
+
+	private $_timings;
+
+	protected function calculateTimings()
+	{
+		if ($this->_timings !== null) {
+			return $this->_timings;
+		}
+		$messages = $this->data['messages'];
+		$timings = [];
+		$stack = [];
+		foreach ($messages as $i => $log) {
+			list($token, $level, $category, $timestamp) = $log;
+			$log[5] = $i;
+			if ($level == Logger::LEVEL_PROFILE_BEGIN) {
+				$stack[] = $log;
+			} elseif ($level == Logger::LEVEL_PROFILE_END) {
+				if (($last = array_pop($stack)) !== null && $last[0] === $token) {
+					$timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]];
+				}
+			}
+		}
+
+		$now = microtime(true);
+		while (($last = array_pop($stack)) !== null) {
+			$delta = $now - $last[3];
+			$timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]];
+		}
+		ksort($timings);
+		return $this->_timings = $timings;
+	}
+
+	public function save()
+	{
+		$target = $this->module->logTarget;
+		$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\elasticsearch\Connection::httpRequest']);
+		return ['messages' => $messages];
+	}
+}
diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php
index 5ed77e6..9201f9a 100644
--- a/extensions/elasticsearch/QueryBuilder.php
+++ b/extensions/elasticsearch/QueryBuilder.php
@@ -14,7 +14,6 @@ use yii\helpers\Json;
 /**
  * QueryBuilder builds an elasticsearch query based on the specification given as a [[Query]] object.
  *
- *
  * @author Carsten Brandt <mail@cebe.cc>
  * @since 2.0
  */
@@ -247,7 +246,7 @@ class QueryBuilder extends \yii\base\Object
 		}
 
 		if (count($column) > 1) {
-			return $this->buildCompositeInCondition($operator, $column, $values, $params);
+			return $this->buildCompositeInCondition($operator, $column, $values);
 		} elseif (is_array($column)) {
 			$column = reset($column);
 		}
@@ -289,61 +288,10 @@ class QueryBuilder extends \yii\base\Object
 	protected function buildCompositeInCondition($operator, $columns, $values)
 	{
 		throw new NotSupportedException('composite in is not supported by elasticsearch.');
-		$vss = array();
-		foreach ($values as $value) {
-			$vs = array();
-			foreach ($columns as $column) {
-				if (isset($value[$column])) {
-					$phName = self::PARAM_PREFIX . count($params);
-					$params[$phName] = $value[$column];
-					$vs[] = $phName;
-				} else {
-					$vs[] = 'NULL';
-				}
-			}
-			$vss[] = '(' . implode(', ', $vs) . ')';
-		}
-		foreach ($columns as $i => $column) {
-			if (strpos($column, '(') === false) {
-				$columns[$i] = $this->db->quoteColumnName($column);
-			}
-		}
-		return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
 	}
 
 	private function buildLikeCondition($operator, $operands)
 	{
 		throw new NotSupportedException('like conditions is not supported by elasticsearch.');
-		if (!isset($operands[0], $operands[1])) {
-			throw new Exception("Operator '$operator' requires two operands.");
-		}
-
-		list($column, $values) = $operands;
-
-		$values = (array)$values;
-
-		if (empty($values)) {
-			return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0==1' : '';
-		}
-
-		if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
-			$andor = ' AND ';
-		} else {
-			$andor = ' OR ';
-			$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
-		}
-
-		if (strpos($column, '(') === false) {
-			$column = $this->db->quoteColumnName($column);
-		}
-
-		$parts = array();
-		foreach ($values as $value) {
-			$phName = self::PARAM_PREFIX . count($params);
-			$params[$phName] = $value;
-			$parts[] = "$column $operator $phName";
-		}
-
-		return implode($andor, $parts);
 	}
 }
diff --git a/extensions/elasticsearch/README.md b/extensions/elasticsearch/README.md
index 57497e2..2a07155 100644
--- a/extensions/elasticsearch/README.md
+++ b/extensions/elasticsearch/README.md
@@ -146,3 +146,29 @@ $query->search(); // gives you all the records + stats about the visit_count fie
 ```
 
 And there is so much more in it. "it’s endless what you can build"[¹](http://www.elasticsearch.org/)
+
+
+Using the elasticsearch DebugPanel
+----------------------------------
+
+The yii2 elasticsearch extensions provides a `DebugPanel` that can be integrated with the yii debug module
+an shows the executed elasticsearch queries. It also allows to run these queries on different cluster nodes
+an view the results.
+
+Add the following to you application config to enable it:
+
+```php
+	// ...
+	'preload' => 'debug',
+	'modules' => [
+		'debug' => [
+			'class' => 'yii\\debug\\Module',
+			'panels' => [
+				'elasticsearch' => [
+					'class' => 'yii\\elasticsearch\\DebugPanel',
+				],
+			],
+		],
+	],
+	// ...
+```
diff --git a/extensions/gii/Generator.php b/extensions/gii/Generator.php
index 53e54bb..05c45a7 100644
--- a/extensions/gii/Generator.php
+++ b/extensions/gii/Generator.php
@@ -63,7 +63,7 @@ abstract class Generator extends Model
 	abstract public function generate();
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function init()
 	{
@@ -164,7 +164,7 @@ abstract class Generator extends Model
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 *
 	 * Child classes should override this method like the following so that the parent
 	 * rules are included:
diff --git a/extensions/gii/GiiAsset.php b/extensions/gii/GiiAsset.php
index 64dc62b..b100750 100644
--- a/extensions/gii/GiiAsset.php
+++ b/extensions/gii/GiiAsset.php
@@ -18,25 +18,25 @@ use yii\web\AssetBundle;
 class GiiAsset extends AssetBundle
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public $sourcePath = '@yii/gii/assets';
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public $css = [
 		'main.css',
 		'typeahead.js-bootstrap.css',
 	];
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public $js = [
 		'gii.js',
 		'typeahead.js',
 	];
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public $depends = [
 		'yii\web\YiiAsset',
diff --git a/extensions/gii/Module.php b/extensions/gii/Module.php
index 5644e29..a7bb3ed 100644
--- a/extensions/gii/Module.php
+++ b/extensions/gii/Module.php
@@ -8,7 +8,7 @@
 namespace yii\gii;
 
 use Yii;
-use yii\web\HttpException;
+use yii\web\AccessDeniedHttpException;
 
 /**
  * This is the main module class for the Gii module.
@@ -54,7 +54,7 @@ use yii\web\HttpException;
 class Module extends \yii\base\Module
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public $controllerNamespace = 'yii\gii\controllers';
 	/**
@@ -92,7 +92,7 @@ class Module extends \yii\base\Module
 
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function init()
 	{
@@ -103,14 +103,14 @@ class Module extends \yii\base\Module
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function beforeAction($action)
 	{
 		if ($this->checkAccess()) {
 			return parent::beforeAction($action);
 		} else {
-			throw new HttpException(403, 'You are not allowed to access this page.');
+			throw new AccessDeniedHttpException('You are not allowed to access this page.');
 		}
 	}
 
diff --git a/extensions/gii/controllers/DefaultController.php b/extensions/gii/controllers/DefaultController.php
index ef104c3..2b3bf00 100644
--- a/extensions/gii/controllers/DefaultController.php
+++ b/extensions/gii/controllers/DefaultController.php
@@ -9,7 +9,7 @@ namespace yii\gii\controllers;
 
 use Yii;
 use yii\web\Controller;
-use yii\web\HttpException;
+use yii\web\NotFoundHttpException;
 
 /**
  * @author Qiang Xue <qiang.xue@gmail.com>
@@ -69,7 +69,7 @@ class DefaultController extends Controller
 				}
 			}
 		}
-		throw new HttpException(404, "Code file not found: $file");
+		throw new NotFoundHttpException("Code file not found: $file");
 	}
 
 	public function actionDiff($id, $file)
@@ -84,7 +84,7 @@ class DefaultController extends Controller
 				}
 			}
 		}
-		throw new HttpException(404, "Code file not found: $file");
+		throw new NotFoundHttpException("Code file not found: $file");
 	}
 
 	/**
@@ -94,7 +94,7 @@ class DefaultController extends Controller
 	 * @param string $id the ID of the generator
 	 * @param string $name the action name
 	 * @return mixed the result of the action.
-	 * @throws HttpException if the action method does not exist.
+	 * @throws NotFoundHttpException if the action method does not exist.
 	 */
 	public function actionAction($id, $name)
 	{
@@ -103,7 +103,7 @@ class DefaultController extends Controller
 		if (method_exists($generator, $method)) {
 			return $generator->$method();
 		} else {
-			throw new HttpException(400, "Unknown generator action: $name");
+			throw new NotFoundHttpException("Unknown generator action: $name");
 		}
 	}
 
@@ -136,7 +136,7 @@ class DefaultController extends Controller
 	 * Loads the generator with the specified ID.
 	 * @param string $id the ID of the generator to be loaded.
 	 * @return \yii\gii\Generator the loaded generator
-	 * @throws \yii\web\HttpException
+	 * @throws NotFoundHttpException
 	 */
 	protected function loadGenerator($id)
 	{
@@ -146,7 +146,7 @@ class DefaultController extends Controller
 			$this->generator->load($_POST);
 			return $this->generator;
 		} else {
-			throw new HttpException(404, "Code generator not found: $id");
+			throw new NotFoundHttpException("Code generator not found: $id");
 		}
 	}
 }
diff --git a/extensions/gii/generators/controller/Generator.php b/extensions/gii/generators/controller/Generator.php
index 769db0a..08b29d5 100644
--- a/extensions/gii/generators/controller/Generator.php
+++ b/extensions/gii/generators/controller/Generator.php
@@ -38,7 +38,7 @@ class Generator extends \yii\gii\Generator
 	public $actions = 'index';
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function init()
 	{
@@ -47,7 +47,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getName()
 	{
@@ -55,7 +55,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getDescription()
 	{
@@ -64,7 +64,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function rules()
 	{
@@ -79,7 +79,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attributeLabels()
 	{
@@ -92,7 +92,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function requiredTemplates()
 	{
@@ -103,7 +103,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function stickyAttributes()
 	{
@@ -111,7 +111,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function hints()
 	{
@@ -134,7 +134,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function successMessage()
 	{
@@ -149,7 +149,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function generate()
 	{
diff --git a/extensions/gii/generators/crud/Generator.php b/extensions/gii/generators/crud/Generator.php
index 107c8f2..4b31d70 100644
--- a/extensions/gii/generators/crud/Generator.php
+++ b/extensions/gii/generators/crud/Generator.php
@@ -9,6 +9,7 @@ namespace yii\gii\generators\crud;
 
 use Yii;
 use yii\db\ActiveRecord;
+use yii\db\BaseActiveRecord;
 use yii\db\Schema;
 use yii\gii\CodeFile;
 use yii\helpers\Inflector;
@@ -46,7 +47,7 @@ class Generator extends \yii\gii\Generator
 			[['modelClass', 'searchModelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'],
 			[['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'],
 			[['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'],
-			[['modelClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]],
+			[['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]],
 			[['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]],
 			[['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'],
 			[['controllerClass', 'searchModelClass'], 'validateNewClass'],
@@ -69,7 +70,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function hints()
 	{
@@ -95,7 +96,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function stickyAttributes()
 	{
@@ -123,7 +124,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function generate()
 	{
@@ -167,13 +168,13 @@ class Generator extends \yii\gii\Generator
 
 	public function getNameAttribute()
 	{
-		/** @var \yii\db\ActiveRecord $class */
-		$class = $this->modelClass;
-		foreach ($class::getTableSchema()->columnNames as $name) {
+		foreach ($this->getColumnNames() as $name) {
 			if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) {
 				return $name;
 			}
 		}
+		/** @var \yii\db\ActiveRecord $class */
+		$class = $this->modelClass;
 		$pk = $class::primaryKey();
 		return $pk[0];
 	}
@@ -185,8 +186,12 @@ class Generator extends \yii\gii\Generator
 	public function generateActiveField($attribute)
 	{
 		$tableSchema = $this->getTableSchema();
-		if (!isset($tableSchema->columns[$attribute])) {
-			return "\$form->field(\$model, '$attribute');";
+		if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) {
+			if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) {
+				return "\$form->field(\$model, '$attribute')->passwordInput();";
+			} else {
+				return "\$form->field(\$model, '$attribute');";
+			}
 		}
 		$column = $tableSchema->columns[$attribute];
 		if ($column->phpType === 'boolean') {
@@ -214,6 +219,9 @@ class Generator extends \yii\gii\Generator
 	public function generateActiveSearchField($attribute)
 	{
 		$tableSchema = $this->getTableSchema();
+		if ($tableSchema === false) {
+			return "\$form->field(\$model, '$attribute')";
+		}
 		$column = $tableSchema->columns[$attribute];
 		if ($column->phpType === 'boolean') {
 			return "\$form->field(\$model, '$attribute')->checkbox()";
@@ -249,7 +257,9 @@ class Generator extends \yii\gii\Generator
 	 */
 	public function generateSearchRules()
 	{
-		$table = $this->getTableSchema();
+		if (($table = $this->getTableSchema()) === false) {
+			return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"];
+		}
 		$types = [];
 		foreach ($table->columns as $column) {
 			switch ($column->type) {
@@ -286,7 +296,7 @@ class Generator extends \yii\gii\Generator
 
 	public function getSearchAttributes()
 	{
-		return $this->getTableSchema()->getColumnNames();
+		return $this->getColumnNames();
 	}
 
 	/**
@@ -295,17 +305,16 @@ class Generator extends \yii\gii\Generator
 	 */
 	public function generateSearchLabels()
 	{
-		$table = $this->getTableSchema();
 		$labels = [];
-		foreach ($table->columns as $column) {
-			if (!strcasecmp($column->name, 'id')) {
-				$labels[$column->name] = 'ID';
+		foreach ($this->getColumnNames() as $name) {
+			if (!strcasecmp($name, 'id')) {
+				$labels[$name] = 'ID';
 			} else {
-				$label = Inflector::camel2words($column->name);
+				$label = Inflector::camel2words($name);
 				if (strcasecmp(substr($label, -3), ' id') === 0) {
 					$label = substr($label, 0, -3) . ' ID';
 				}
-				$labels[$column->name] = $label;
+				$labels[$name] = $label;
 			}
 		}
 		return $labels;
@@ -313,10 +322,21 @@ class Generator extends \yii\gii\Generator
 
 	public function generateSearchConditions()
 	{
-		$table = $this->getTableSchema();
+		$columns = [];
+		if (($table = $this->getTableSchema()) === false) {
+			$class = $this->modelClass;
+			$model = new $class();
+			foreach ($model->attributes() as $attribute) {
+				$columns[$attribute] = 'unknown';
+			}
+		} else {
+			foreach ($table->columns as $column) {
+				$columns[$column->name] = $column->type;
+			}
+		}
 		$conditions = [];
-		foreach ($table->columns as $column) {
-			switch ($column->type) {
+		foreach ($columns as $column => $type) {
+			switch ($type) {
 				case Schema::TYPE_SMALLINT:
 				case Schema::TYPE_INTEGER:
 				case Schema::TYPE_BIGINT:
@@ -328,10 +348,11 @@ class Generator extends \yii\gii\Generator
 				case Schema::TYPE_TIME:
 				case Schema::TYPE_DATETIME:
 				case Schema::TYPE_TIMESTAMP:
-					$conditions[] = "\$this->addCondition(\$query, '{$column->name}');";
+				case
+					$conditions[] = "\$this->addCondition(\$query, '{$column}');";
 					break;
 				default:
-					$conditions[] = "\$this->addCondition(\$query, '{$column->name}', true);";
+					$conditions[] = "\$this->addCondition(\$query, '{$column}', true);";
 					break;
 			}
 		}
@@ -369,10 +390,16 @@ class Generator extends \yii\gii\Generator
 
 	public function generateActionParamComments()
 	{
-		$table = $this->getTableSchema();
 		/** @var ActiveRecord $class */
 		$class = $this->modelClass;
 		$pks = $class::primaryKey();
+		if (($table = $this->getTableSchema()) === false) {
+			$params = [];
+			foreach ($pks as $pk) {
+				$params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk;
+			}
+			return $params;
+		}
 		if (count($pks) === 1) {
 			return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id'];
 		} else {
@@ -388,6 +415,23 @@ class Generator extends \yii\gii\Generator
 	{
 		/** @var ActiveRecord $class */
 		$class = $this->modelClass;
-		return $class::getTableSchema();
+		if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
+			return $class::getTableSchema();
+		} else {
+			return false;
+		}
 	}
+
+	public function getColumnNames()
+	{
+		/** @var ActiveRecord $class */
+		$class = $this->modelClass;
+		if (is_subclass_of($class, 'yii\db\ActiveRecord')) {
+			return $class::getTableSchema()->getColumnNames();
+		} else {
+			$model = new $class();
+			return $model->attributes();
+		}
+	}
+
 }
diff --git a/extensions/gii/generators/crud/templates/controller.php b/extensions/gii/generators/crud/templates/controller.php
index f975fd1..bb5d3f4 100644
--- a/extensions/gii/generators/crud/templates/controller.php
+++ b/extensions/gii/generators/crud/templates/controller.php
@@ -1,5 +1,6 @@
 <?php
 
+use yii\db\ActiveRecordInterface;
 use yii\helpers\StringHelper;
 
 /**
@@ -16,7 +17,9 @@ if ($modelClass === $searchModelClass) {
 	$searchModelAlias = $searchModelClass.'Search';
 }
 
-$pks = $generator->getTableSchema()->primaryKey;
+/** @var ActiveRecordInterface $class */
+$class = $generator->modelClass;
+$pks = $class::primaryKey();
 $urlParams = $generator->generateUrlParams();
 $actionParams = $generator->generateActionParams();
 $actionParamComments = $generator->generateActionParamComments();
@@ -29,7 +32,7 @@ namespace <?= StringHelper::dirname(ltrim($generator->controllerClass, '\\')) ?>
 use <?= ltrim($generator->modelClass, '\\') ?>;
 use <?= ltrim($generator->searchModelClass, '\\') ?><?php if (isset($searchModelAlias)):?> as <?= $searchModelAlias ?><?php endif ?>;
 use <?= ltrim($generator->baseControllerClass, '\\') ?>;
-use yii\web\HttpException;
+use yii\web\NotFoundHttpException;
 use yii\web\VerbFilter;
 
 /**
@@ -130,7 +133,7 @@ class <?= $controllerClass ?> extends <?= StringHelper::basename($generator->bas
 	 * If the model is not found, a 404 HTTP exception will be thrown.
 	 * <?= implode("\n\t * ", $actionParamComments) . "\n" ?>
 	 * @return <?= $modelClass ?> the loaded model
-	 * @throws HttpException if the model cannot be found
+	 * @throws NotFoundHttpException if the model cannot be found
 	 */
 	protected function findModel(<?= $actionParams ?>)
 	{
@@ -148,7 +151,7 @@ if (count($pks) === 1) {
 		if (($model = <?= $modelClass ?>::find(<?= $condition ?>)) !== null) {
 			return $model;
 		} else {
-			throw new HttpException(404, 'The requested page does not exist.');
+			throw new NotFoundHttpException('The requested page does not exist.');
 		}
 	}
 }
diff --git a/extensions/gii/generators/crud/templates/search.php b/extensions/gii/generators/crud/templates/search.php
index 17a0024..1411896 100644
--- a/extensions/gii/generators/crud/templates/search.php
+++ b/extensions/gii/generators/crud/templates/search.php
@@ -40,7 +40,7 @@ class <?= $searchModelClass ?> extends Model
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attributeLabels()
 	{
diff --git a/extensions/gii/generators/crud/templates/views/_form.php b/extensions/gii/generators/crud/templates/views/_form.php
index c93ef84..52538d5 100644
--- a/extensions/gii/generators/crud/templates/views/_form.php
+++ b/extensions/gii/generators/crud/templates/views/_form.php
@@ -12,7 +12,7 @@ use yii\helpers\StringHelper;
 $model = new $generator->modelClass;
 $safeAttributes = $model->safeAttributes();
 if (empty($safeAttributes)) {
-	$safeAttributes = $model->getTableSchema()->columnNames;
+	$safeAttributes = $model->attributes();
 }
 
 echo "<?php\n";
diff --git a/extensions/gii/generators/crud/templates/views/_search.php b/extensions/gii/generators/crud/templates/views/_search.php
index 0e23a5c..af2f948 100644
--- a/extensions/gii/generators/crud/templates/views/_search.php
+++ b/extensions/gii/generators/crud/templates/views/_search.php
@@ -30,7 +30,7 @@ use yii\widgets\ActiveForm;
 
 <?php
 $count = 0;
-foreach ($generator->getTableSchema()->getColumnNames() as $attribute) {
+foreach ($generator->getColumnNames() as $attribute) {
 	if (++$count < 6) {
 		echo "\t\t<?= " . $generator->generateActiveSearchField($attribute) . " ?>\n\n";
 	} else {
diff --git a/extensions/gii/generators/crud/templates/views/index.php b/extensions/gii/generators/crud/templates/views/index.php
index b47c3f3..3ad2138 100644
--- a/extensions/gii/generators/crud/templates/views/index.php
+++ b/extensions/gii/generators/crud/templates/views/index.php
@@ -45,12 +45,22 @@ $this->params['breadcrumbs'][] = $this->title;
 
 <?php
 $count = 0;
-foreach ($generator->getTableSchema()->columns as $column) {
-	$format = $generator->generateColumnFormat($column);
-	if (++$count < 6) {
-		echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n";
-	} else {
-		echo "\t\t\t// '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n";
+if (($tableSchema = $generator->getTableSchema()) === false) {
+	foreach ($generator->getColumnNames() as $name) {
+		if (++$count < 6) {
+			echo "\t\t\t'" . $name . "',\n";
+		} else {
+			echo "\t\t\t// '" . $name . "',\n";
+		}
+	}
+} else {
+	foreach ($tableSchema->columns as $column) {
+		$format = $generator->generateColumnFormat($column);
+		if (++$count < 6) {
+			echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n";
+		} else {
+			echo "\t\t\t// '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n";
+		}
 	}
 }
 ?>
diff --git a/extensions/gii/generators/crud/templates/views/view.php b/extensions/gii/generators/crud/templates/views/view.php
index 9b5391e..9e74aff 100644
--- a/extensions/gii/generators/crud/templates/views/view.php
+++ b/extensions/gii/generators/crud/templates/views/view.php
@@ -42,9 +42,15 @@ $this->params['breadcrumbs'][] = $this->title;
 		'model' => $model,
 		'attributes' => [
 <?php
-foreach ($generator->getTableSchema()->columns as $column) {
-	$format = $generator->generateColumnFormat($column);
-	echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n";
+if (($tableSchema = $generator->getTableSchema()) === false) {
+	foreach ($generator->getColumnNames() as $name) {
+		echo "\t\t\t'" . $name . "',\n";
+	}
+} else {
+	foreach ($generator->getTableSchema()->columns as $column) {
+		$format = $generator->generateColumnFormat($column);
+		echo "\t\t\t'" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n";
+	}
 }
 ?>
 		],
diff --git a/extensions/gii/generators/form/Generator.php b/extensions/gii/generators/form/Generator.php
index cc4328e..3bc0be6 100644
--- a/extensions/gii/generators/form/Generator.php
+++ b/extensions/gii/generators/form/Generator.php
@@ -26,7 +26,7 @@ class Generator extends \yii\gii\Generator
 
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getName()
 	{
@@ -34,7 +34,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getDescription()
 	{
@@ -42,7 +42,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function generate()
 	{
@@ -55,7 +55,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function rules()
 	{
@@ -72,7 +72,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attributeLabels()
 	{
@@ -85,7 +85,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function requiredTemplates()
 	{
@@ -93,7 +93,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function stickyAttributes()
 	{
@@ -101,7 +101,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function hints()
 	{
@@ -114,7 +114,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function successMessage()
 	{
diff --git a/extensions/gii/generators/model/Generator.php b/extensions/gii/generators/model/Generator.php
index 8c505f1..cd2fcbf 100644
--- a/extensions/gii/generators/model/Generator.php
+++ b/extensions/gii/generators/model/Generator.php
@@ -32,7 +32,7 @@ class Generator extends \yii\gii\Generator
 
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getName()
 	{
@@ -40,7 +40,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getDescription()
 	{
@@ -48,7 +48,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function rules()
 	{
@@ -68,7 +68,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attributeLabels()
 	{
@@ -84,7 +84,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function hints()
 	{
@@ -111,7 +111,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function autoCompleteData()
 	{
@@ -128,7 +128,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function requiredTemplates()
 	{
@@ -136,7 +136,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function stickyAttributes()
 	{
@@ -144,7 +144,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function generate()
 	{
diff --git a/extensions/gii/generators/model/templates/model.php b/extensions/gii/generators/model/templates/model.php
index 555bee1..dcd1461 100644
--- a/extensions/gii/generators/model/templates/model.php
+++ b/extensions/gii/generators/model/templates/model.php
@@ -33,7 +33,7 @@ namespace <?= $generator->ns ?>;
 class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . "\n" ?>
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public static function tableName()
 	{
@@ -41,7 +41,7 @@ class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . 
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function rules()
 	{
@@ -49,7 +49,7 @@ class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . 
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attributeLabels()
 	{
diff --git a/extensions/gii/generators/module/Generator.php b/extensions/gii/generators/module/Generator.php
index a29555b..5946e07 100644
--- a/extensions/gii/generators/module/Generator.php
+++ b/extensions/gii/generators/module/Generator.php
@@ -24,7 +24,7 @@ class Generator extends \yii\gii\Generator
 	public $moduleID;
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getName()
 	{
@@ -32,7 +32,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getDescription()
 	{
@@ -40,7 +40,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function rules()
 	{
@@ -54,7 +54,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attributeLabels()
 	{
@@ -65,7 +65,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function hints()
 	{
@@ -76,7 +76,7 @@ class Generator extends \yii\gii\Generator
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function successMessage()
 	{
@@ -104,7 +104,7 @@ EOD;
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function requiredTemplates()
 	{
@@ -112,7 +112,7 @@ EOD;
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function generate()
 	{
diff --git a/extensions/redis/ActiveQuery.php b/extensions/redis/ActiveQuery.php
index 755fc6f..607b18e 100644
--- a/extensions/redis/ActiveQuery.php
+++ b/extensions/redis/ActiveQuery.php
@@ -132,7 +132,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
 			if ($db === null) {
 				$db = $modelClass::getDb();
 			}
-			return $db->executeCommand('LLEN', [$modelClass::tableName()]);
+			return $db->executeCommand('LLEN', [$modelClass::keyPrefix()]);
 		} else {
 			return $this->executeScript($db, 'Count');
 		}
@@ -296,7 +296,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
 		$data = [];
 		foreach($pks as $pk) {
 			if (++$i > $start && ($this->limit === null || $i <= $start + $this->limit)) {
-				$key = $modelClass::tableName() . ':a:' . $modelClass::buildKey($pk);
+				$key = $modelClass::keyPrefix() . ':a:' . $modelClass::buildKey($pk);
 				$result = $db->executeCommand('HGETALL', [$key]);
 				if (!empty($result)) {
 					$data[] = $result;
diff --git a/extensions/redis/ActiveRecord.php b/extensions/redis/ActiveRecord.php
index 8c2933c..f227ed1 100644
--- a/extensions/redis/ActiveRecord.php
+++ b/extensions/redis/ActiveRecord.php
@@ -9,6 +9,8 @@ namespace yii\redis;
 
 use yii\base\InvalidConfigException;
 use yii\base\NotSupportedException;
+use yii\db\BaseActiveRecord;
+use yii\helpers\Inflector;
 use yii\helpers\StringHelper;
 
 /**
@@ -34,7 +36,7 @@ use yii\helpers\StringHelper;
  * @author Carsten Brandt <mail@cebe.cc>
  * @since 2.0
  */
-class ActiveRecord extends \yii\db\ActiveRecord
+class ActiveRecord extends BaseActiveRecord
 {
 	/**
 	 * Returns the database connection used by this AR class.
@@ -48,7 +50,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public static function createQuery()
 	{
@@ -56,7 +58,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public static function createActiveRelation($config = [])
 	{
@@ -87,7 +89,19 @@ class ActiveRecord extends \yii\db\ActiveRecord
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * Declares prefix of the key that represents the keys that store this records in redis.
+	 * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]].
+	 * For example, 'Customer' becomes 'customer', and 'OrderItem' becomes
+	 * 'order_item'. You may override this method if you want different key naming.
+	 * @return string the prefix to apply to all AR keys
+	 */
+	public static function keyPrefix()
+	{
+		return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
+	}
+
+	/**
+	 * @inheritdoc
 	 */
 	public function insert($runValidation = true, $attributes = null)
 	{
@@ -102,15 +116,15 @@ class ActiveRecord extends \yii\db\ActiveRecord
 			foreach ($this->primaryKey() as $key) {
 				$pk[$key] = $values[$key] = $this->getAttribute($key);
 				if ($pk[$key] === null) {
-					$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::tableName() . ':s:' . $key]);
+					$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]);
 					$this->setAttribute($key, $values[$key]);
 				}
 			}
 //			}
 			// save pk in a findall pool
-			$db->executeCommand('RPUSH', [static::tableName(), static::buildKey($pk)]);
+			$db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]);
 
-			$key = static::tableName() . ':a:' . static::buildKey($pk);
+			$key = static::keyPrefix() . ':a:' . static::buildKey($pk);
 			// save attributes
 			$args = [$key];
 			foreach($values as $attribute => $value) {
@@ -150,7 +164,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 		foreach(static::fetchPks($condition) as $pk) {
 			$newPk = $pk;
 			$pk = static::buildKey($pk);
-			$key = static::tableName() . ':a:' . $pk;
+			$key = static::keyPrefix() . ':a:' . $pk;
 			// save attributes
 			$args = [$key];
 			foreach($attributes as $attribute => $value) {
@@ -161,13 +175,13 @@ class ActiveRecord extends \yii\db\ActiveRecord
 				$args[] = $value;
 			}
 			$newPk = static::buildKey($newPk);
-			$newKey = static::tableName() . ':a:' . $newPk;
+			$newKey = static::keyPrefix() . ':a:' . $newPk;
 			// rename index if pk changed
 			if ($newPk != $pk) {
 				$db->executeCommand('MULTI');
 				$db->executeCommand('HMSET', $args);
-				$db->executeCommand('LINSERT', [static::tableName(), 'AFTER', $pk, $newPk]);
-				$db->executeCommand('LREM', [static::tableName(), 0, $pk]);
+				$db->executeCommand('LINSERT', [static::keyPrefix(), 'AFTER', $pk, $newPk]);
+				$db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
 				$db->executeCommand('RENAME', [$key, $newKey]);
 				$db->executeCommand('EXEC');
 			} else {
@@ -201,7 +215,7 @@ class ActiveRecord extends \yii\db\ActiveRecord
 		$db = static::getDb();
 		$n=0;
 		foreach(static::fetchPks($condition) as $pk) {
-			$key = static::tableName() . ':a:' . static::buildKey($pk);
+			$key = static::keyPrefix() . ':a:' . static::buildKey($pk);
 			foreach($counters as $attribute => $value) {
 				$db->executeCommand('HINCRBY', [$key, $attribute, $value]);
 			}
@@ -233,8 +247,8 @@ class ActiveRecord extends \yii\db\ActiveRecord
 		$db->executeCommand('MULTI');
 		foreach($pks as $pk) {
 			$pk = static::buildKey($pk);
-			$db->executeCommand('LREM', [static::tableName(), 0, $pk]);
-			$attributeKeys[] = static::tableName() . ':a:' . $pk;
+			$db->executeCommand('LREM', [static::keyPrefix(), 0, $pk]);
+			$attributeKeys[] = static::keyPrefix() . ':a:' . $pk;
 		}
 		if (empty($attributeKeys)) {
 			$db->executeCommand('EXEC');
@@ -292,31 +306,4 @@ class ActiveRecord extends \yii\db\ActiveRecord
 		}
 		return md5(json_encode($key));
 	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public static function getTableSchema()
-	{
-		throw new NotSupportedException('getTableSchema() is not supported by redis ActiveRecord.');
-	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public static function findBySql($sql, $params = [])
-	{
-		throw new NotSupportedException('findBySql() is not supported by redis ActiveRecord.');
-	}
-
-	/**
-	 * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
-	 * This method will always return false as transactional operations are not supported by redis.
-	 * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
-	 * @return boolean whether the specified operation is transactional in the current [[scenario]].
-	 */
-	public function isTransactional($operation)
-	{
-		return false;
-	}
 }
diff --git a/extensions/redis/Cache.php b/extensions/redis/Cache.php
index 627acdc..0722b9d 100644
--- a/extensions/redis/Cache.php
+++ b/extensions/redis/Cache.php
@@ -105,7 +105,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function getValue($key)
 	{
@@ -113,7 +113,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function getValues($keys)
 	{
@@ -127,7 +127,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function setValue($key, $value, $expire)
 	{
@@ -140,7 +140,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function setValues($data, $expire)
 	{
@@ -174,7 +174,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function addValue($key, $value, $expire)
 	{
@@ -187,7 +187,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function deleteValue($key)
 	{
@@ -195,7 +195,7 @@ class Cache extends \yii\caching\Cache
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function flushValues()
 	{
diff --git a/extensions/redis/LuaScriptBuilder.php b/extensions/redis/LuaScriptBuilder.php
index 81dff3f..55bf455 100644
--- a/extensions/redis/LuaScriptBuilder.php
+++ b/extensions/redis/LuaScriptBuilder.php
@@ -29,7 +29,7 @@ class LuaScriptBuilder extends \yii\base\Object
 		// TODO add support for orderBy
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "n=n+1 pks[n]=redis.call('HGETALL',$key .. pk)", 'pks');
 	}
 
@@ -43,7 +43,7 @@ class LuaScriptBuilder extends \yii\base\Object
 		// TODO add support for orderBy
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "do return redis.call('HGETALL',$key .. pk) end", 'pks');
 	}
 
@@ -58,7 +58,7 @@ class LuaScriptBuilder extends \yii\base\Object
 		// TODO add support for orderBy and indexBy
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "n=n+1 pks[n]=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'pks');
 	}
 
@@ -82,7 +82,7 @@ class LuaScriptBuilder extends \yii\base\Object
 	{
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "n=n+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'n');
 	}
 
@@ -96,7 +96,7 @@ class LuaScriptBuilder extends \yii\base\Object
 	{
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "n=n+1 if v==nil then v=0 end v=v+redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ")", 'v/n');
 	}
 
@@ -110,7 +110,7 @@ class LuaScriptBuilder extends \yii\base\Object
 	{
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n<v then v=n end", 'v');
 	}
 
@@ -124,7 +124,7 @@ class LuaScriptBuilder extends \yii\base\Object
 	{
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName() . ':a:');
+		$key = $this->quoteValue($modelClass::keyPrefix() . ':a:');
 		return $this->build($query, "n=redis.call('HGET',$key .. pk," . $this->quoteValue($column) . ") if v==nil or n>v then v=n end", 'v');
 	}
 
@@ -152,7 +152,7 @@ class LuaScriptBuilder extends \yii\base\Object
 
 		/** @var ActiveRecord $modelClass */
 		$modelClass = $query->modelClass;
-		$key = $this->quoteValue($modelClass::tableName());
+		$key = $this->quoteValue($modelClass::keyPrefix());
 		$loadColumnValues = '';
 		foreach($columns as $column => $alias) {
 			$loadColumnValues .= "local $alias=redis.call('HGET',$key .. ':a:' .. pk, '$column')\n";
diff --git a/extensions/sphinx/ActiveQuery.php b/extensions/sphinx/ActiveQuery.php
index 7ba48d2..3533c35 100644
--- a/extensions/sphinx/ActiveQuery.php
+++ b/extensions/sphinx/ActiveQuery.php
@@ -176,7 +176,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function defaultConnection()
 	{
@@ -201,4 +201,4 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 		}
 		return $result;
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/sphinx/Command.php b/extensions/sphinx/Command.php
index a6c8c4f..ba802f5 100644
--- a/extensions/sphinx/Command.php
+++ b/extensions/sphinx/Command.php
@@ -197,7 +197,7 @@ class Command extends \yii\db\Command
 	// Not Supported :
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function createTable($table, $columns, $options = null)
 	{
@@ -205,7 +205,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function renameTable($table, $newName)
 	{
@@ -213,7 +213,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function dropTable($table)
 	{
@@ -221,7 +221,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function truncateTable($table)
 	{
@@ -229,7 +229,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function addColumn($table, $column, $type)
 	{
@@ -237,7 +237,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function dropColumn($table, $column)
 	{
@@ -245,7 +245,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function renameColumn($table, $oldName, $newName)
 	{
@@ -253,7 +253,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function alterColumn($table, $column, $type)
 	{
@@ -261,7 +261,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function addPrimaryKey($name, $table, $columns)
 	{
@@ -269,7 +269,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function dropPrimaryKey($name, $table)
 	{
@@ -277,7 +277,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
 	{
@@ -285,7 +285,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function dropForeignKey($name, $table)
 	{
@@ -293,7 +293,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function createIndex($name, $table, $columns, $unique = false)
 	{
@@ -301,7 +301,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function dropIndex($name, $table)
 	{
@@ -309,7 +309,7 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function resetSequence($table, $value = null)
 	{
@@ -317,10 +317,10 @@ class Command extends \yii\db\Command
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function checkIntegrity($check = true, $schema = '')
 	{
 		throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/sphinx/Connection.php b/extensions/sphinx/Connection.php
index a3492bb..b732d97 100644
--- a/extensions/sphinx/Connection.php
+++ b/extensions/sphinx/Connection.php
@@ -52,9 +52,6 @@ use yii\base\NotSupportedException;
  *
  * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
  * sequence object. This property is read-only.
- * @property Schema $schema The schema information for this Sphinx connection. This property is read-only.
- * @property \yii\sphinx\QueryBuilder $queryBuilder The query builder for this Sphinx connection. This property is
- * read-only.
  *
  * @author Paul Klimov <klimov.paul@gmail.com>
  * @since 2.0
@@ -62,7 +59,7 @@ use yii\base\NotSupportedException;
 class Connection extends \yii\db\Connection
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public $schemaMap = [
 		'mysqli' => 'yii\sphinx\Schema',   // MySQL
@@ -129,4 +126,4 @@ class Connection extends \yii\db\Connection
 	{
 		throw new NotSupportedException('"' . __METHOD__ . '" is not supported.');
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/sphinx/README.md b/extensions/sphinx/README.md
index ae7c285..5f02204 100644
--- a/extensions/sphinx/README.md
+++ b/extensions/sphinx/README.md
@@ -1,17 +1,7 @@
-Yii 2.0 Public Preview - Sphinx Extension
-=========================================
+Sphinx Extension for Yii 2
+==========================
 
-Thank you for choosing Yii - a high-performance component-based PHP framework.
-
-If you are looking for a production-ready PHP framework, please use
-[Yii v1.1](https://github.com/yiisoft/yii).
-
-Yii 2.0 is still under heavy development. We may make significant changes
-without prior notices. **Yii 2.0 is not ready for production use yet.**
-
-[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2)
-
-This is the yii2-sphinx extension.
+This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii 2 framework.
 
 
 Installation
@@ -20,26 +10,26 @@ Installation
 The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
 
 Either run
+
 ```
 php composer.phar require yiisoft/yii2-sphinx "*"
 ```
 
 or add
-```
+
+```json
 "yiisoft/yii2-sphinx": "*"
 ```
-to the require section of your composer.json.
-
 
-*Note: You might have to run `php composer.phar selfupdate`*
+to the require section of your composer.json.
 
 
 Usage & Documentation
 ---------------------
 
-This extension adds [Sphinx](http://sphinxsearch.com/docs) full text search engine extension for the Yii framework.
-This extension interact with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language.
+This extension interacts with Sphinx search daemon using MySQL protocol and [SphinxQL](http://sphinxsearch.com/docs/current.html#sphinxql) query language.
 In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added:
+
 ```
 searchd
 {
@@ -115,4 +105,4 @@ $provider = new ActiveDataProvider([
 	]
 ]);
 $models = $provider->getModels();
-```
\ No newline at end of file
+```
diff --git a/extensions/sphinx/composer.json b/extensions/sphinx/composer.json
index 9a323d7..6026022 100644
--- a/extensions/sphinx/composer.json
+++ b/extensions/sphinx/composer.json
@@ -17,7 +17,6 @@
 			"email": "klimov.paul@gmail.com"
 		}
 	],
-	"minimum-stability": "dev",
 	"require": {
 		"yiisoft/yii2": "*",
 		"ext-pdo": "*",
@@ -25,5 +24,6 @@
 	},
 	"autoload": {
 		"psr-0": { "yii\\sphinx\\": "" }
-	}
+	},
+	"target-dir": "yii/sphinx"
 }
diff --git a/extensions/swiftmailer/Mailer.php b/extensions/swiftmailer/Mailer.php
index aab8434..891f2b5 100644
--- a/extensions/swiftmailer/Mailer.php
+++ b/extensions/swiftmailer/Mailer.php
@@ -123,7 +123,7 @@ class Mailer extends BaseMailer
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function sendMessage($message)
 	{
diff --git a/extensions/swiftmailer/Message.php b/extensions/swiftmailer/Message.php
index f7d8b34..9558d58 100644
--- a/extensions/swiftmailer/Message.php
+++ b/extensions/swiftmailer/Message.php
@@ -41,7 +41,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getCharset()
 	{
@@ -49,7 +49,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setCharset($charset)
 	{
@@ -58,7 +58,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getFrom()
 	{
@@ -66,7 +66,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setFrom($from)
 	{
@@ -75,7 +75,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getReplyTo()
 	{
@@ -83,7 +83,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setReplyTo($replyTo)
 	{
@@ -92,7 +92,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getTo()
 	{
@@ -100,7 +100,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setTo($to)
 	{
@@ -109,7 +109,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getCc()
 	{
@@ -117,7 +117,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setCc($cc)
 	{
@@ -126,7 +126,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getBcc()
 	{
@@ -134,7 +134,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setBcc($bcc)
 	{
@@ -143,7 +143,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function getSubject()
 	{
@@ -151,7 +151,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setSubject($subject)
 	{
@@ -160,7 +160,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setTextBody($text)
 	{
@@ -169,7 +169,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setHtmlBody($html)
 	{
@@ -222,7 +222,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attach($fileName, array $options = [])
 	{
@@ -238,7 +238,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function attachContent($content, array $options = [])
 	{
@@ -254,7 +254,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function embed($fileName, array $options = [])
 	{
@@ -269,7 +269,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function embedContent($content, array $options = [])
 	{
@@ -284,7 +284,7 @@ class Message extends BaseMessage
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function toString()
 	{
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index 90e0268..9fe5c8d 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -1,7 +1,7 @@
 Yii Framework 2 Change Log
 ==========================
 
-Version 2
----------
+Version 2.0 alpha
+-----------------
 
-- Initial release.
\ No newline at end of file
+- Initial release.
diff --git a/framework/README.md b/framework/README.md
index b5c754f..0d146ce 100644
--- a/framework/README.md
+++ b/framework/README.md
@@ -1,21 +1,24 @@
-Yii 2.0 Public Preview
-======================
+Yii PHP Framework Version 2
+===========================
 
-Thank you for choosing Yii - a high-performance component-based PHP framework.
+This is the core framework code of [Yii 2](https://github.com/yiisoft/yii2).
 
-If you are looking for a production-ready PHP framework, please use
-[Yii v1.1](https://github.com/yiisoft/yii).
 
-Yii 2.0 is still under heavy development. We may make significant changes
-without prior notices. **Yii 2.0 is not ready for production use yet.**
-
-[![Build Status](https://secure.travis-ci.org/yiisoft/yii2.png)](http://travis-ci.org/yiisoft/yii2)
+Installation
+------------
 
+The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
 
+Either run
 
-REQUIREMENTS
-------------
+```
+php composer.phar require yiisoft/yii2-framework "*"
+```
 
-The minimum requirement by Yii is that your Web server supports PHP 5.4.0.
+or add
 
+```json
+"yiisoft/yii2-framework": "*"
+```
 
+to the require section of your composer.json.
diff --git a/framework/composer.json b/framework/composer.json
index 8260adb..bcd3927 100644
--- a/framework/composer.json
+++ b/framework/composer.json
@@ -1,6 +1,6 @@
 {
 	"name": "yiisoft/yii2",
-	"description": "Yii2 Web Programming Framework",
+	"description": "Yii PHP Framework Version 2",
 	"keywords": ["yii", "framework"],
 	"homepage": "http://www.yiiframework.com/",
 	"type": "library",
@@ -39,21 +39,6 @@
 			"name": "Paul Klimov",
 			"email": "klimov.paul@gmail.com",
 			"role": "Core framework development"
-		},
-		{
-			"name": "Wei Zhuo",
-			"email": "weizhuo@gmail.com",
-			"role": "Project site maintenance and development"
-		},
-		{
-			"name": "Sebastián Thierer",
-			"email": "sebas@artfos.com",
-			"role": "Component development"
-		},
-		{
-			"name": "Jeffrey Winesett",
-			"email": "jefftulsa@gmail.com",
-			"role": "Documentation and marketing"
 		}
 	],
 	"support": {
diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js
index b9f2cdd..c5904a1 100644
--- a/framework/yii/assets/yii.js
+++ b/framework/yii/assets/yii.js
@@ -58,14 +58,14 @@ yii = (function ($) {
 		changeableSelector: 'select, input, textarea',
 
 		/**
-		 * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled.
+		 * @return string|undefined the CSRF variable name. Undefined is returned if CSRF validation is not enabled.
 		 */
 		getCsrfVar: function () {
 			return $('meta[name=csrf-var]').prop('content');
 		},
 
 		/**
-		 * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled.
+		 * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled.
 		 */
 		getCsrfToken: function () {
 			return $('meta[name=csrf-token]').prop('content');
diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php
index 48b526c..f135b40 100644
--- a/framework/yii/base/Application.php
+++ b/framework/yii/base/Application.php
@@ -23,6 +23,7 @@ use yii\web\HttpException;
  * @property \yii\base\Formatter $formatter The formatter application component. This property is read-only.
  * @property \yii\i18n\I18N $i18n The internationalization component. This property is read-only.
  * @property \yii\log\Logger $log The log component. This property is read-only.
+ * @property \yii\mail\MailerInterface $mail The mailer interface. This property is read-only.
  * @property \yii\web\Request|\yii\console\Request $request The request component. This property is read-only.
  * @property string $runtimePath The directory that stores runtime files. Defaults to the "runtime"
  * subdirectory under [[basePath]].
@@ -199,12 +200,12 @@ abstract class Application extends Module
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function init()
 	{
-		parent::init();
 		$this->initExtensions($this->extensions);
+		parent::init();
 	}
 
 	/**
@@ -634,9 +635,9 @@ abstract class Application extends Module
 	{
 		$category = get_class($exception);
 		if ($exception instanceof HttpException) {
-			$category .= '\\' . $exception->statusCode;
+			$category = 'yii\\web\\HttpException:' . $exception->statusCode;
 		} elseif ($exception instanceof \ErrorException) {
-			$category .= '\\' . $exception->getSeverity();
+			$category .= ':' . $exception->getSeverity();
 		}
 		Yii::error((string)$exception, $category);
 	}
diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php
index 42a2efc..d14c9dd 100644
--- a/framework/yii/base/Controller.php
+++ b/framework/yii/base/Controller.php
@@ -194,7 +194,7 @@ class Controller extends Component implements ViewContextInterface
 		$actionMap = $this->actions();
 		if (isset($actionMap[$id])) {
 			return Yii::createObject($actionMap[$id], $id, $this);
-		} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
+		} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
 			$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
 			if (method_exists($this, $methodName)) {
 				$method = new \ReflectionMethod($this, $methodName);
@@ -417,9 +417,13 @@ class Controller extends Component implements ViewContextInterface
 			$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
 		}
 
-		if (pathinfo($file, PATHINFO_EXTENSION) === '') {
-			$file .= $view->defaultExtension;
+		if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
+			return $file;
 		}
-		return $file;
+		$path = $file . '.' . $view->defaultExtension;
+		if ($view->defaultExtension !== 'php' && !is_file($path)) {
+			$path = $file . '.php';
+		}
+		return $path;
 	}
 }
diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php
index fcfe31e..47650f0 100644
--- a/framework/yii/base/View.php
+++ b/framework/yii/base/View.php
@@ -67,7 +67,7 @@ class View extends Component
 	/**
 	 * @var string the default view file extension. This will be appended to view file names if they don't have file extensions.
 	 */
-	public $defaultExtension = '.php';
+	public $defaultExtension = 'php';
 	/**
 	 * @var Theme|array the theme object or the configuration array for creating the theme object.
 	 * If not set, it means theming is not enabled.
@@ -171,7 +171,14 @@ class View extends Component
 			}
 		}
 
-		return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . $this->defaultExtension : $file;
+		if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
+			return $file;
+		}
+		$path = $file . '.' . $this->defaultExtension;
+		if ($this->defaultExtension !== 'php' && !is_file($path)) {
+			$path = $file . '.php';
+		}
+		return $path;
 	}
 
 	/**
diff --git a/framework/yii/base/Widget.php b/framework/yii/base/Widget.php
index a0f7795..5e3ce2b 100644
--- a/framework/yii/base/Widget.php
+++ b/framework/yii/base/Widget.php
@@ -14,7 +14,8 @@ use ReflectionClass;
  * Widget is the base class for widgets.
  *
  * @property string $id ID of the widget.
- * @property View $view The view object that can be used to render views or view files.
+ * @property \yii\web\View $view The view object that can be used to render views or view files. Note that the
+ * type of this property differs in getter and setter. See [[getView()]] and [[setView()]] for details.
  * @property string $viewPath The directory containing the view files for this widget. This property is
  * read-only.
  *
@@ -121,7 +122,7 @@ class Widget extends Component implements ViewContextInterface
 	 * The [[render()]] and [[renderFile()]] methods will use
 	 * this view object to implement the actual view rendering.
 	 * If not set, it will default to the "view" application component.
-	 * @return View the view object that can be used to render views or view files.
+	 * @return \yii\web\View the view object that can be used to render views or view files.
 	 */
 	public function getView()
 	{
diff --git a/framework/yii/caching/WinCache.php b/framework/yii/caching/WinCache.php
index 7f1eca8..6b80f3c 100644
--- a/framework/yii/caching/WinCache.php
+++ b/framework/yii/caching/WinCache.php
@@ -13,7 +13,7 @@ namespace yii\caching;
  * To use this application component, the [WinCache PHP extension](http://www.iis.net/expand/wincacheforphp)
  * must be loaded. Also note that "wincache.ucenabled" should be set to "On" in your php.ini file.
  *
- * See {@link CCache} manual for common cache operations that are supported by WinCache.
+ * See [[Cache]] manual for common cache operations that are supported by WinCache.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
diff --git a/framework/yii/classes.php b/framework/yii/classes.php
index c8b2935..9606930 100644
--- a/framework/yii/classes.php
+++ b/framework/yii/classes.php
@@ -74,13 +74,16 @@ return [
 	'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php',
 	'yii\data\Pagination' => YII_PATH . '/data/Pagination.php',
 	'yii\data\Sort' => YII_PATH . '/data/Sort.php',
+	'yii\data\SqlDataProvider' => YII_PATH . '/data/SqlDataProvider.php',
 	'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php',
 	'yii\db\ActiveQueryInterface' => YII_PATH . '/db/ActiveQueryInterface.php',
 	'yii\db\ActiveQueryTrait' => YII_PATH . '/db/ActiveQueryTrait.php',
 	'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php',
+	'yii\db\ActiveRecordInterface' => YII_PATH . '/db/ActiveRecordInterface.php',
 	'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php',
 	'yii\db\ActiveRelationInterface' => YII_PATH . '/db/ActiveRelationInterface.php',
 	'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php',
+	'yii\db\BaseActiveRecord' => YII_PATH . '/db/BaseActiveRecord.php',
 	'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php',
 	'yii\db\Command' => YII_PATH . '/db/Command.php',
 	'yii\db\Connection' => YII_PATH . '/db/Connection.php',
@@ -192,12 +195,14 @@ return [
 	'yii\validators\ValidationAsset' => YII_PATH . '/validators/ValidationAsset.php',
 	'yii\validators\Validator' => YII_PATH . '/validators/Validator.php',
 	'yii\web\AccessControl' => YII_PATH . '/web/AccessControl.php',
+	'yii\web\AccessDeniedHttpException' => YII_PATH . '/web/AccessDeniedHttpException.php',
 	'yii\web\AccessRule' => YII_PATH . '/web/AccessRule.php',
 	'yii\web\Application' => YII_PATH . '/web/Application.php',
 	'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php',
 	'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php',
 	'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php',
 	'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php',
+	'yii\web\BadRequestHttpException' => YII_PATH . '/web/BadRequestHttpException.php',
 	'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php',
 	'yii\web\Controller' => YII_PATH . '/web/Controller.php',
 	'yii\web\Cookie' => YII_PATH . '/web/Cookie.php',
@@ -210,6 +215,8 @@ return [
 	'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php',
 	'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php',
 	'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php',
+	'yii\web\MethodNotAllowedHttpException' => YII_PATH . '/web/MethodNotAllowedHttpException.php',
+	'yii\web\NotFoundHttpException' => YII_PATH . '/web/NotFoundHttpException.php',
 	'yii\web\PageCache' => YII_PATH . '/web/PageCache.php',
 	'yii\web\Request' => YII_PATH . '/web/Request.php',
 	'yii\web\Response' => YII_PATH . '/web/Response.php',
diff --git a/framework/yii/console/controllers/MessageController.php b/framework/yii/console/controllers/MessageController.php
index 8a0f10b..7ac1558 100644
--- a/framework/yii/console/controllers/MessageController.php
+++ b/framework/yii/console/controllers/MessageController.php
@@ -107,11 +107,13 @@ class MessageController extends Controller
 				@mkdir($dir);
 			}
 			foreach ($messages as $category => $msgs) {
+				$file = str_replace("\\", '/', "$dir/$category.php");
+				$path = dirname($file);
+				if (!is_dir($path)) {
+					mkdir($path, 0755, true);
+				}
 				$msgs = array_values(array_unique($msgs));
-				$this->generateMessageFile($msgs, $dir . DIRECTORY_SEPARATOR . $category . '.php',
-					$config['overwrite'],
-					$config['removeUnused'],
-					$config['sort']);
+				$this->generateMessageFile($msgs, $file, $config['overwrite'], $config['removeUnused'], $config['sort']);
 			}
 		}
 	}
diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php
index 0b3fc4a..2292286 100644
--- a/framework/yii/data/ActiveDataProvider.php
+++ b/framework/yii/data/ActiveDataProvider.php
@@ -86,14 +86,14 @@ class ActiveDataProvider extends BaseDataProvider
 		parent::init();
 		if (is_string($this->db)) {
 			$this->db = Yii::$app->getComponent($this->db);
-			if (!$this->db instanceof Connection) {
+			if ($this->db === null) {
 				throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
 			}
 		}
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function prepareModels()
 	{
@@ -111,7 +111,7 @@ class ActiveDataProvider extends BaseDataProvider
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function prepareKeys($models)
 	{
@@ -138,9 +138,9 @@ class ActiveDataProvider extends BaseDataProvider
 				foreach ($models as $model) {
 					$kk = [];
 					foreach ($pks as $pk) {
-						$kk[] = $model[$pk];
+						$kk[$pk] = $model[$pk];
 					}
-					$keys[] = json_encode($kk);
+					$keys[] = $kk;
 				}
 			}
 			return $keys;
@@ -150,7 +150,7 @@ class ActiveDataProvider extends BaseDataProvider
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function prepareTotalCount()
 	{
@@ -162,7 +162,7 @@ class ActiveDataProvider extends BaseDataProvider
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function setSort($value)
 	{
diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php
index 987e364..2b694c7 100644
--- a/framework/yii/data/ArrayDataProvider.php
+++ b/framework/yii/data/ArrayDataProvider.php
@@ -66,7 +66,7 @@ class ArrayDataProvider extends BaseDataProvider
 
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function prepareModels()
 	{
@@ -87,7 +87,7 @@ class ArrayDataProvider extends BaseDataProvider
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function prepareKeys($models)
 	{
@@ -107,7 +107,7 @@ class ArrayDataProvider extends BaseDataProvider
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	protected function prepareTotalCount()
 	{
diff --git a/framework/yii/data/SqlDataProvider.php b/framework/yii/data/SqlDataProvider.php
new file mode 100644
index 0000000..d8f0cd1
--- /dev/null
+++ b/framework/yii/data/SqlDataProvider.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\data;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\base\Model;
+use yii\db\Connection;
+
+/**
+ * SqlDataProvider implements a data provider based on a plain SQL statement.
+ *
+ * SqlDataProvider provides data in terms of arrays, each representing a row of query result.
+ *
+ * Like other data providers, SqlDataProvider also supports sorting and pagination.
+ * It does so by modifying the given [[sql]] statement with "ORDER BY" and "LIMIT"
+ * clauses. You may configure the [[sort]] and [[pagination]] properties to
+ * customize sorting and pagination behaviors.
+ *
+ * SqlDataProvider may be used in the following way:
+ *
+ * ~~~
+ * $count = Yii::$app->db->createCommand('
+ *     SELECT COUNT(*) FROM tbl_user WHERE status=:status
+ * ', [':status' => 1])->queryScalar();
+ *
+ * $dataProvider = new SqlDataProvider([
+ *     'sql' => 'SELECT * FROM tbl_user WHERE status=:status',
+ *     'params' => [':status' => 1],
+ *     'totalCount' => $count,
+ *     'sort' => [
+ *         'attributes' => [
+ *             'age',
+ *             'name' => [
+ *                 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
+ *                 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
+ *                 'default' => SORT_DESC,
+ *                 'label' => 'Name',
+ *             ],
+ *         ],
+ *     ],
+ *     'pagination' => [
+ *         'pageSize' => 20,
+ *     ],
+ * ]);
+ *
+ * // get the user records in the current page
+ * $models = $dataProvider->getModels();
+ * ~~~
+ *
+ * Note: if you want to use the pagination feature, you must configure the [[totalCount]] property
+ * to be the total number of rows (without pagination). And if you want to use the sorting feature,
+ * you must configure the [[sort]] property so that the provider knows which columns can be sorted.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class SqlDataProvider extends BaseDataProvider
+{
+	/**
+	 * @var Connection|string the DB connection object or the application component ID of the DB connection.
+	 * If not set, the default DB connection will be used.
+	 */
+	public $db;
+	/**
+	 * @var string the SQL statement to be used for fetching data rows.
+	 */
+	public $sql;
+	/**
+	 * @var array parameters (name=>value) to be bound to the SQL statement.
+	 */
+	public $params = [];
+	/**
+	 * @var string|callable the column that is used as the key of the data models.
+	 * This can be either a column name, or a callable that returns the key value of a given data model.
+	 *
+	 * If this is not set, the keys of the [[models]] array will be used.
+	 */
+	public $key;
+
+
+	/**
+	 * Initializes the DB connection component.
+	 * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+	 * @throws InvalidConfigException if [[db]] is invalid.
+	 */
+	public function init()
+	{
+		parent::init();
+		if (is_string($this->db)) {
+			$this->db = Yii::$app->getComponent($this->db);
+		}
+		if (!$this->db instanceof Connection) {
+			throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
+		}
+		if ($this->sql === null) {
+			throw new InvalidConfigException('The "sql" property must be set.');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function prepareModels()
+	{
+		$sql = $this->sql;
+		$qb = $this->db->getQueryBuilder();
+		if (($sort = $this->getSort()) !== false) {
+			$orderBy = $qb->buildOrderBy($sort->getOrders());
+			if (!empty($orderBy)) {
+				$orderBy = substr($orderBy, 9);
+				if (preg_match('/\s+order\s+by\s+[\w\s,\.]+$/i', $sql)) {
+					$sql .= ', ' . $orderBy;
+				} else {
+					$sql .= ' ORDER BY ' . $orderBy;
+				}
+			}
+		}
+
+		if (($pagination = $this->getPagination()) !== false) {
+			$pagination->totalCount = $this->getTotalCount();
+			$sql .= ' ' . $qb->buildLimit($pagination->getLimit(), $pagination->getOffset());
+		}
+
+		return $this->db->createCommand($sql, $this->params)->queryAll();
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function prepareKeys($models)
+	{
+		$keys = [];
+		if ($this->key !== null) {
+			foreach ($models as $model) {
+				if (is_string($this->key)) {
+					$keys[] = $model[$this->key];
+				} else {
+					$keys[] = call_user_func($this->key, $model);
+				}
+			}
+			return $keys;
+		} else {
+			return array_keys($models);
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function prepareTotalCount()
+	{
+		return 0;
+	}
+}
diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php
index bb2016a..adcf508 100644
--- a/framework/yii/db/ActiveRecord.php
+++ b/framework/yii/db/ActiveRecord.php
@@ -22,62 +22,13 @@ use yii\helpers\Inflector;
  *
  * @include @yii/db/ActiveRecord.md
  *
- * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is
- * read-only.
- * @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]].
- * @property array $oldAttributes The old attribute values (name-value pairs).
- * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is
- * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key
- * value is null). This property is read-only.
- * @property array $populatedRelations An array of relation data indexed by relation names. This property is
- * read-only.
- * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if
- * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null).
- * This property is read-only.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @author Carsten Brandt <mail@cebe.cc>
  * @since 2.0
  */
-class ActiveRecord extends Model
+class ActiveRecord extends BaseActiveRecord
 {
 	/**
-	 * @event Event an event that is triggered when the record is initialized via [[init()]].
-	 */
-	const EVENT_INIT = 'init';
-	/**
-	 * @event Event an event that is triggered after the record is created and populated with query result.
-	 */
-	const EVENT_AFTER_FIND = 'afterFind';
-	/**
-	 * @event ModelEvent an event that is triggered before inserting a record.
-	 * You may set [[ModelEvent::isValid]] to be false to stop the insertion.
-	 */
-	const EVENT_BEFORE_INSERT = 'beforeInsert';
-	/**
-	 * @event Event an event that is triggered after a record is inserted.
-	 */
-	const EVENT_AFTER_INSERT = 'afterInsert';
-	/**
-	 * @event ModelEvent an event that is triggered before updating a record.
-	 * You may set [[ModelEvent::isValid]] to be false to stop the update.
-	 */
-	const EVENT_BEFORE_UPDATE = 'beforeUpdate';
-	/**
-	 * @event Event an event that is triggered after a record is updated.
-	 */
-	const EVENT_AFTER_UPDATE = 'afterUpdate';
-	/**
-	 * @event ModelEvent an event that is triggered before deleting a record.
-	 * You may set [[ModelEvent::isValid]] to be false to stop the deletion.
-	 */
-	const EVENT_BEFORE_DELETE = 'beforeDelete';
-	/**
-	 * @event Event an event that is triggered after a record is deleted.
-	 */
-	const EVENT_AFTER_DELETE = 'afterDelete';
-
-	/**
 	 * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
 	 */
 	const OP_INSERT = 0x01;
@@ -96,20 +47,6 @@ class ActiveRecord extends Model
 	const OP_ALL = 0x07;
 
 	/**
-	 * @var array attribute values indexed by attribute names
-	 */
-	private $_attributes = [];
-	/**
-	 * @var array old attribute values indexed by attribute names.
-	 */
-	private $_oldAttributes;
-	/**
-	 * @var array related models indexed by the relation names
-	 */
-	private $_related = [];
-
-
-	/**
 	 * Returns the database connection used by this AR class.
 	 * By default, the "db" application component is used as the database connection.
 	 * You may override this method if you want to use a different database connection.
@@ -121,41 +58,6 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * Creates an [[ActiveQuery]] instance for query purpose.
-	 *
-	 * @include @yii/db/ActiveRecord-find.md
-	 *
-	 * @param mixed $q the query parameter. This can be one of the followings:
-	 *
-	 *  - a scalar value (integer or string): query by a single primary key value and return the
-	 *    corresponding record.
-	 *  - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
-	 *  - null: return a new [[ActiveQuery]] object for further query purpose.
-	 *
-	 * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance
-	 * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
-	 * returned (null will be returned if there is no matching).
-	 * @throws InvalidConfigException if the AR class does not have a primary key
-	 * @see createQuery()
-	 */
-	public static function find($q = null)
-	{
-		$query = static::createQuery();
-		if (is_array($q)) {
-			return $query->where($q)->one();
-		} elseif ($q !== null) {
-			// query by primary key
-			$primaryKey = static::primaryKey();
-			if (isset($primaryKey[0])) {
-				return $query->where([$primaryKey[0] => $q])->one();
-			} else {
-				throw new InvalidConfigException(get_called_class() . ' must have a primary key.');
-			}
-		}
-		return $query;
-	}
-
-	/**
 	 * Creates an [[ActiveQuery]] instance with a given SQL statement.
 	 *
 	 * Note that because the SQL statement is already specified, calling additional
@@ -309,31 +211,13 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * Returns the name of the column that stores the lock version for implementing optimistic locking.
-	 *
-	 * Optimistic locking allows multiple users to access the same record for edits and avoids
-	 * potential conflicts. In case when a user attempts to save the record upon some staled data
-	 * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
-	 * and the update or deletion is skipped.
-	 *
-	 * Optimistic locking is only supported by [[update()]] and [[delete()]].
-	 *
-	 * To use Optimistic locking:
-	 *
-	 * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
-	 *    Override this method to return the name of this column.
-	 * 2. In the Web form that collects the user input, add a hidden field that stores
-	 *    the lock version of the recording being updated.
-	 * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
-	 *    and implement necessary business logic (e.g. merging the changes, prompting stated data)
-	 *    to resolve the conflict.
-	 *
-	 * @return string the column name that stores the lock version of a table row.
-	 * If null is returned (default implemented), optimistic locking will not be supported.
+	 * Returns the list of all attribute names of the model.
+	 * The default implementation will return all column names of the table associated with this AR class.
+	 * @return array list of attribute names.
 	 */
-	public function optimisticLock()
+	public function attributes()
 	{
-		return null;
+		return array_keys(static::getTableSchema()->columns);
 	}
 
 	/**
@@ -369,161 +253,6 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * PHP getter magic method.
-	 * This method is overridden so that attributes and related objects can be accessed like properties.
-	 * @param string $name property name
-	 * @return mixed property value
-	 * @see getAttribute()
-	 */
-	public function __get($name)
-	{
-		if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
-			return $this->_attributes[$name];
-		} elseif ($this->hasAttribute($name)) {
-			return null;
-		} else {
-			if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
-				return $this->_related[$name];
-			}
-			$value = parent::__get($name);
-			if ($value instanceof ActiveRelationInterface) {
-				return $this->_related[$name] = $value->multiple ? $value->all() : $value->one();
-			} else {
-				return $value;
-			}
-		}
-	}
-
-	/**
-	 * PHP setter magic method.
-	 * This method is overridden so that AR attributes can be accessed like properties.
-	 * @param string $name property name
-	 * @param mixed $value property value
-	 */
-	public function __set($name, $value)
-	{
-		if ($this->hasAttribute($name)) {
-			$this->_attributes[$name] = $value;
-		} else {
-			parent::__set($name, $value);
-		}
-	}
-
-	/**
-	 * Checks if a property value is null.
-	 * This method overrides the parent implementation by checking if the named attribute is null or not.
-	 * @param string $name the property name or the event name
-	 * @return boolean whether the property value is null
-	 */
-	public function __isset($name)
-	{
-		try {
-			return $this->__get($name) !== null;
-		} catch (\Exception $e) {
-			return false;
-		}
-	}
-
-	/**
-	 * Sets a component property to be null.
-	 * This method overrides the parent implementation by clearing
-	 * the specified attribute value.
-	 * @param string $name the property name or the event name
-	 */
-	public function __unset($name)
-	{
-		if ($this->hasAttribute($name)) {
-			unset($this->_attributes[$name]);
-		} else {
-			if (isset($this->_related[$name])) {
-				unset($this->_related[$name]);
-			} else {
-				parent::__unset($name);
-			}
-		}
-	}
-
-	/**
-	 * Declares a `has-one` relation.
-	 * The declaration is returned in terms of an [[ActiveRelation]] instance
-	 * through which the related record can be queried and retrieved back.
-	 *
-	 * A `has-one` relation means that there is at most one related record matching
-	 * the criteria set by this relation, e.g., a customer has one country.
-	 *
-	 * For example, to declare the `country` relation for `Customer` class, we can write
-	 * the following code in the `Customer` class:
-	 *
-	 * ~~~
-	 * public function getCountry()
-	 * {
-	 *     return $this->hasOne(Country::className(), ['id' => 'country_id']);
-	 * }
-	 * ~~~
-	 *
-	 * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
-	 * in the related class `Country`, while the 'country_id' value refers to an attribute name
-	 * in the current AR class.
-	 *
-	 * Call methods declared in [[ActiveRelation]] to further customize the relation.
-	 *
-	 * @param string $class the class name of the related record
-	 * @param array $link the primary-foreign key constraint. The keys of the array refer to
-	 * the attributes of the record associated with the `$class` model, while the values of the
-	 * array refer to the corresponding attributes in **this** AR class.
-	 * @return ActiveRelationInterface the relation object.
-	 */
-	public function hasOne($class, $link)
-	{
-		/** @var ActiveRecord $class */
-		return $class::createActiveRelation([
-			'modelClass' => $class,
-			'primaryModel' => $this,
-			'link' => $link,
-			'multiple' => false,
-		]);
-	}
-
-	/**
-	 * Declares a `has-many` relation.
-	 * The declaration is returned in terms of an [[ActiveRelation]] instance
-	 * through which the related record can be queried and retrieved back.
-	 *
-	 * A `has-many` relation means that there are multiple related records matching
-	 * the criteria set by this relation, e.g., a customer has many orders.
-	 *
-	 * For example, to declare the `orders` relation for `Customer` class, we can write
-	 * the following code in the `Customer` class:
-	 *
-	 * ~~~
-	 * public function getOrders()
-	 * {
-	 *     return $this->hasMany(Order::className(), ['customer_id' => 'id']);
-	 * }
-	 * ~~~
-	 *
-	 * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
-	 * an attribute name in the related class `Order`, while the 'id' value refers to
-	 * an attribute name in the current AR class.
-	 *
-	 * @param string $class the class name of the related record
-	 * @param array $link the primary-foreign key constraint. The keys of the array refer to
-	 * the attributes of the record associated with the `$class` model, while the values of the
-	 * array refer to the corresponding attributes in **this** AR class.
-	 * @return ActiveRelationInterface the relation object.
-	 */
-	public function hasMany($class, $link)
-	{
-		/** @var ActiveRecord $class */
-		return $class::createActiveRelation([
-			'modelClass' => $class,
-			'primaryModel' => $this,
-			'link' => $link,
-			'multiple' => true,
-		]);
-	}
-
-	/**
 	 * Creates an [[ActiveRelation]] instance.
 	 * This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
 	 * You may override this method to return a customized relation.
@@ -536,208 +265,6 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * Populates the named relation with the related records.
-	 * Note that this method does not check if the relation exists or not.
-	 * @param string $name the relation name (case-sensitive)
-	 * @param ActiveRecord|array|null the related records to be populated into the relation.
-	 */
-	public function populateRelation($name, $records)
-	{
-		$this->_related[$name] = $records;
-	}
-
-	/**
-	 * Check whether the named relation has been populated with records.
-	 * @param string $name the relation name (case-sensitive)
-	 * @return bool whether relation has been populated with records.
-	 */
-	public function isRelationPopulated($name)
-	{
-		return array_key_exists($name, $this->_related);
-	}
-
-	/**
-	 * Returns all populated relations.
-	 * @return array an array of relation data indexed by relation names.
-	 */
-	public function getPopulatedRelations()
-	{
-		return $this->_related;
-	}
-
-	/**
-	 * Returns the list of all attribute names of the model.
-	 * The default implementation will return all column names of the table associated with this AR class.
-	 * @return array list of attribute names.
-	 */
-	public function attributes()
-	{
-		return array_keys(static::getTableSchema()->columns);
-	}
-
-	/**
-	 * Returns a value indicating whether the model has an attribute with the specified name.
-	 * @param string $name the name of the attribute
-	 * @return boolean whether the model has an attribute with the specified name.
-	 */
-	public function hasAttribute($name)
-	{
-		return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
-	}
-
-	/**
-	 * Returns the named attribute value.
-	 * If this record is the result of a query and the attribute is not loaded,
-	 * null will be returned.
-	 * @param string $name the attribute name
-	 * @return mixed the attribute value. Null if the attribute is not set or does not exist.
-	 * @see hasAttribute()
-	 */
-	public function getAttribute($name)
-	{
-		return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
-	}
-
-	/**
-	 * Sets the named attribute value.
-	 * @param string $name the attribute name
-	 * @param mixed $value the attribute value.
-	 * @throws InvalidParamException if the named attribute does not exist.
-	 * @see hasAttribute()
-	 */
-	public function setAttribute($name, $value)
-	{
-		if ($this->hasAttribute($name)) {
-			$this->_attributes[$name] = $value;
-		} else {
-			throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
-		}
-	}
-
-	/**
-	 * Returns the old attribute values.
-	 * @return array the old attribute values (name-value pairs)
-	 */
-	public function getOldAttributes()
-	{
-		return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
-	}
-
-	/**
-	 * Sets the old attribute values.
-	 * All existing old attribute values will be discarded.
-	 * @param array $values old attribute values to be set.
-	 */
-	public function setOldAttributes($values)
-	{
-		$this->_oldAttributes = $values;
-	}
-
-	/**
-	 * Returns the old value of the named attribute.
-	 * If this record is the result of a query and the attribute is not loaded,
-	 * null will be returned.
-	 * @param string $name the attribute name
-	 * @return mixed the old attribute value. Null if the attribute is not loaded before
-	 * or does not exist.
-	 * @see hasAttribute()
-	 */
-	public function getOldAttribute($name)
-	{
-		return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
-	}
-
-	/**
-	 * Sets the old value of the named attribute.
-	 * @param string $name the attribute name
-	 * @param mixed $value the old attribute value.
-	 * @throws InvalidParamException if the named attribute does not exist.
-	 * @see hasAttribute()
-	 */
-	public function setOldAttribute($name, $value)
-	{
-		if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
-			$this->_oldAttributes[$name] = $value;
-		} else {
-			throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
-		}
-	}
-
-	/**
-	 * Returns a value indicating whether the named attribute has been changed.
-	 * @param string $name the name of the attribute
-	 * @return boolean whether the attribute has been changed
-	 */
-	public function isAttributeChanged($name)
-	{
-		if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
-			return $this->_attributes[$name] !== $this->_oldAttributes[$name];
-		} else {
-			return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
-		}
-	}
-
-	/**
-	 * Returns the attribute values that have been modified since they are loaded or saved most recently.
-	 * @param string[]|null $names the names of the attributes whose values may be returned if they are
-	 * changed recently. If null, [[attributes()]] will be used.
-	 * @return array the changed attribute values (name-value pairs)
-	 */
-	public function getDirtyAttributes($names = null)
-	{
-		if ($names === null) {
-			$names = $this->attributes();
-		}
-		$names = array_flip($names);
-		$attributes = [];
-		if ($this->_oldAttributes === null) {
-			foreach ($this->_attributes as $name => $value) {
-				if (isset($names[$name])) {
-					$attributes[$name] = $value;
-				}
-			}
-		} else {
-			foreach ($this->_attributes as $name => $value) {
-				if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
-					$attributes[$name] = $value;
-				}
-			}
-		}
-		return $attributes;
-	}
-
-	/**
-	 * Saves the current record.
-	 *
-	 * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
-	 * when [[isNewRecord]] is false.
-	 *
-	 * For example, to save a customer record:
-	 *
-	 * ~~~
-	 * $customer = new Customer;  // or $customer = Customer::find($id);
-	 * $customer->name = $name;
-	 * $customer->email = $email;
-	 * $customer->save();
-	 * ~~~
-	 *
-	 *
-	 * @param boolean $runValidation whether to perform validation before saving the record.
-	 * If the validation fails, the record will not be saved to database.
-	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
-	 * meaning all attributes that are loaded from DB will be saved.
-	 * @return boolean whether the saving succeeds
-	 */
-	public function save($runValidation = true, $attributes = null)
-	{
-		if ($this->getIsNewRecord()) {
-			return $this->insert($runValidation, $attributes);
-		} else {
-			return $this->update($runValidation, $attributes) !== false;
-		}
-	}
-
-	/**
 	 * Inserts a row into the associated database table using the attribute values of this record.
 	 *
 	 * This method performs the following steps in order:
@@ -810,8 +337,8 @@ class ActiveRecord extends Model
 		}
 		$values = $this->getDirtyAttributes($attributes);
 		if (empty($values)) {
-			foreach ($this->primaryKey() as $key) {
-				$values[$key] = isset($this->_attributes[$key]) ? $this->_attributes[$key] : null;
+			foreach ($this->getPrimaryKey(true) as $key => $value) {
+				$values[$key] = $value;
 			}
 		}
 		$db = static::getDb();
@@ -822,14 +349,16 @@ class ActiveRecord extends Model
 		$table = $this->getTableSchema();
 		if ($table->sequenceName !== null) {
 			foreach ($table->primaryKey as $name) {
-				if (!isset($this->_attributes[$name])) {
-					$this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
+				if ($this->getAttribute($name) === null) {
+					$id = $db->getLastInsertID($table->sequenceName);
+					$this->setAttribute($name, $id);
+					$this->setOldAttribute($name, $id);
 					break;
 				}
 			}
 		}
 		foreach ($values as $name => $value) {
-			$this->_oldAttributes[$name] = $value;
+			$this->setOldAttribute($name, $value);
 		}
 		$this->afterSave(true);
 		return true;
@@ -911,73 +440,6 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * @see CActiveRecord::update()
-	 * @throws StaleObjectException
-	 */
-	private function updateInternal($attributes = null)
-	{
-		if (!$this->beforeSave(false)) {
-			return false;
-		}
-		$values = $this->getDirtyAttributes($attributes);
-		if (empty($values)) {
-			$this->afterSave(false);
-			return 0;
-		}
-		$condition = $this->getOldPrimaryKey(true);
-		$lock = $this->optimisticLock();
-		if ($lock !== null) {
-			if (!isset($values[$lock])) {
-				$values[$lock] = $this->$lock + 1;
-			}
-			$condition[$lock] = $this->$lock;
-		}
-		// We do not check the return value of updateAll() because it's possible
-		// that the UPDATE statement doesn't change anything and thus returns 0.
-		$rows = $this->updateAll($values, $condition);
-
-		if ($lock !== null && !$rows) {
-			throw new StaleObjectException('The object being updated is outdated.');
-		}
-
-		foreach ($values as $name => $value) {
-			$this->_oldAttributes[$name] = $this->_attributes[$name];
-		}
-		$this->afterSave(false);
-		return $rows;
-	}
-
-	/**
-	 * Updates one or several counter columns for the current AR object.
-	 * Note that this method differs from [[updateAllCounters()]] in that it only
-	 * saves counters for the current AR object.
-	 *
-	 * An example usage is as follows:
-	 *
-	 * ~~~
-	 * $post = Post::find($id);
-	 * $post->updateCounters(['view_count' => 1]);
-	 * ~~~
-	 *
-	 * @param array $counters the counters to be updated (attribute name => increment value)
-	 * Use negative values if you want to decrement the counters.
-	 * @return boolean whether the saving is successful
-	 * @see updateAllCounters()
-	 */
-	public function updateCounters($counters)
-	{
-		if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
-			foreach ($counters as $name => $value) {
-				$this->_attributes[$name] += $value;
-				$this->_oldAttributes[$name] = $this->_attributes[$name];
-			}
-			return true;
-		} else {
-			return false;
-		}
-	}
-
-	/**
 	 * Deletes the table row corresponding to this active record.
 	 *
 	 * This method performs the following steps in order:
@@ -1014,7 +476,7 @@ class ActiveRecord extends Model
 				if ($lock !== null && !$result) {
 					throw new StaleObjectException('The object being deleted is outdated.');
 				}
-				$this->_oldAttributes = null;
+				$this->setOldAttributes(null);
 				$this->afterDelete();
 			}
 			if ($transaction !== null) {
@@ -1034,149 +496,6 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * Returns a value indicating whether the current record is new.
-	 * @return boolean whether the record is new and should be inserted when calling [[save()]].
-	 */
-	public function getIsNewRecord()
-	{
-		return $this->_oldAttributes === null;
-	}
-
-	/**
-	 * Sets the value indicating whether the record is new.
-	 * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
-	 * @see getIsNewRecord()
-	 */
-	public function setIsNewRecord($value)
-	{
-		$this->_oldAttributes = $value ? null : $this->_attributes;
-	}
-
-	/**
-	 * Initializes the object.
-	 * This method is called at the end of the constructor.
-	 * The default implementation will trigger an [[EVENT_INIT]] event.
-	 * If you override this method, make sure you call the parent implementation at the end
-	 * to ensure triggering of the event.
-	 */
-	public function init()
-	{
-		parent::init();
-		$this->trigger(self::EVENT_INIT);
-	}
-
-	/**
-	 * This method is called when the AR object is created and populated with the query result.
-	 * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
-	 * When overriding this method, make sure you call the parent implementation to ensure the
-	 * event is triggered.
-	 */
-	public function afterFind()
-	{
-		$this->trigger(self::EVENT_AFTER_FIND);
-	}
-
-	/**
-	 * This method is called at the beginning of inserting or updating a record.
-	 * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
-	 * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
-	 * When overriding this method, make sure you call the parent implementation like the following:
-	 *
-	 * ~~~
-	 * public function beforeSave($insert)
-	 * {
-	 *     if (parent::beforeSave($insert)) {
-	 *         // ...custom code here...
-	 *         return true;
-	 *     } else {
-	 *         return false;
-	 *     }
-	 * }
-	 * ~~~
-	 *
-	 * @param boolean $insert whether this method called while inserting a record.
-	 * If false, it means the method is called while updating a record.
-	 * @return boolean whether the insertion or updating should continue.
-	 * If false, the insertion or updating will be cancelled.
-	 */
-	public function beforeSave($insert)
-	{
-		$event = new ModelEvent;
-		$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
-		return $event->isValid;
-	}
-
-	/**
-	 * This method is called at the end of inserting or updating a record.
-	 * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
-	 * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
-	 * When overriding this method, make sure you call the parent implementation so that
-	 * the event is triggered.
-	 * @param boolean $insert whether this method called while inserting a record.
-	 * If false, it means the method is called while updating a record.
-	 */
-	public function afterSave($insert)
-	{
-		$this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
-	}
-
-	/**
-	 * This method is invoked before deleting a record.
-	 * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
-	 * When overriding this method, make sure you call the parent implementation like the following:
-	 *
-	 * ~~~
-	 * public function beforeDelete()
-	 * {
-	 *     if (parent::beforeDelete()) {
-	 *         // ...custom code here...
-	 *         return true;
-	 *     } else {
-	 *         return false;
-	 *     }
-	 * }
-	 * ~~~
-	 *
-	 * @return boolean whether the record should be deleted. Defaults to true.
-	 */
-	public function beforeDelete()
-	{
-		$event = new ModelEvent;
-		$this->trigger(self::EVENT_BEFORE_DELETE, $event);
-		return $event->isValid;
-	}
-
-	/**
-	 * This method is invoked after deleting a record.
-	 * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
-	 * You may override this method to do postprocessing after the record is deleted.
-	 * Make sure you call the parent implementation so that the event is raised properly.
-	 */
-	public function afterDelete()
-	{
-		$this->trigger(self::EVENT_AFTER_DELETE);
-	}
-
-	/**
-	 * Repopulates this active record with the latest data.
-	 * @return boolean whether the row still exists in the database. If true, the latest data
-	 * will be populated to this active record. Otherwise, this record will remain unchanged.
-	 */
-	public function refresh()
-	{
-		$record = $this->find($this->getPrimaryKey(true));
-		if ($record === null) {
-			return false;
-		}
-		foreach ($this->attributes() as $name) {
-			$this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
-		}
-		$this->_oldAttributes = $this->_attributes;
-		$this->_related = [];
-		return true;
-	}
-
-	/**
 	 * Returns a value indicating whether the given active record is the same as the current one.
 	 * The comparison is made by comparing the table names and the primary key values of the two active records.
 	 * If one of the records [[isNewRecord|is new]] they are also considered not equal.
@@ -1192,348 +511,6 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * Returns the primary key value(s).
-	 * @param boolean $asArray whether to return the primary key value as an array. If true,
-	 * the return value will be an array with column names as keys and column values as values.
-	 * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
-	 * @property mixed The primary key value. An array (column name => column value) is returned if
-	 * the primary key is composite. A string is returned otherwise (null will be returned if
-	 * the key value is null).
-	 * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
-	 * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
-	 * the key value is null).
-	 */
-	public function getPrimaryKey($asArray = false)
-	{
-		$keys = $this->primaryKey();
-		if (count($keys) === 1 && !$asArray) {
-			return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
-		} else {
-			$values = [];
-			foreach ($keys as $name) {
-				$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
-			}
-			return $values;
-		}
-	}
-
-	/**
-	 * Returns the old primary key value(s).
-	 * This refers to the primary key value that is populated into the record
-	 * after executing a find method (e.g. find(), findAll()).
-	 * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
-	 * @param boolean $asArray whether to return the primary key value as an array. If true,
-	 * the return value will be an array with column name as key and column value as value.
-	 * If this is false (default), a scalar value will be returned for non-composite primary key.
-	 * @property mixed The old primary key value. An array (column name => column value) is
-	 * returned if the primary key is composite. A string is returned otherwise (null will be
-	 * returned if the key value is null).
-	 * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
-	 * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
-	 * the key value is null).
-	 */
-	public function getOldPrimaryKey($asArray = false)
-	{
-		$keys = $this->primaryKey();
-		if (count($keys) === 1 && !$asArray) {
-			return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
-		} else {
-			$values = [];
-			foreach ($keys as $name) {
-				$values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
-			}
-			return $values;
-		}
-	}
-
-	/**
-	 * Creates an active record object using a row of data.
-	 * This method is called by [[ActiveQuery]] to populate the query results
-	 * into Active Records. It is not meant to be used to create new records.
-	 * @param array $row attribute values (name => value)
-	 * @return ActiveRecord the newly created active record.
-	 */
-	public static function create($row)
-	{
-		$record = static::instantiate($row);
-		$columns = array_flip($record->attributes());
-		foreach ($row as $name => $value) {
-			if (isset($columns[$name])) {
-				$record->_attributes[$name] = $value;
-			} else {
-				$record->$name = $value;
-			}
-		}
-		$record->_oldAttributes = $record->_attributes;
-		$record->afterFind();
-		return $record;
-	}
-
-	/**
-	 * Creates an active record instance.
-	 * This method is called by [[create()]].
-	 * You may override this method if the instance being created
-	 * depends on the row data to be populated into the record.
-	 * For example, by creating a record based on the value of a column,
-	 * you may implement the so-called single-table inheritance mapping.
-	 * @param array $row row data to be populated into the record.
-	 * @return ActiveRecord the newly created active record
-	 */
-	public static function instantiate($row)
-	{
-		return new static;
-	}
-
-	/**
-	 * Returns whether there is an element at the specified offset.
-	 * This method is required by the interface ArrayAccess.
-	 * @param mixed $offset the offset to check on
-	 * @return boolean whether there is an element at the specified offset.
-	 */
-	public function offsetExists($offset)
-	{
-		return $this->__isset($offset);
-	}
-
-	/**
-	 * Returns the relation object with the specified name.
-	 * A relation is defined by a getter method which returns an [[ActiveRelation]] object.
-	 * It can be declared in either the Active Record class itself or one of its behaviors.
-	 * @param string $name the relation name
-	 * @return ActiveRelation the relation object
-	 * @throws InvalidParamException if the named relation does not exist.
-	 */
-	public function getRelation($name)
-	{
-		$getter = 'get' . $name;
-		try {
-			$relation = $this->$getter();
-			if ($relation instanceof ActiveRelationInterface) {
-				return $relation;
-			} else {
-				throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
-			}
-		} catch (UnknownMethodException $e) {
-			throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
-		}
-	}
-
-	/**
-	 * Establishes the relationship between two models.
-	 *
-	 * The relationship is established by setting the foreign key value(s) in one model
-	 * to be the corresponding primary key value(s) in the other model.
-	 * The model with the foreign key will be saved into database without performing validation.
-	 *
-	 * If the relationship involves a pivot table, a new row will be inserted into the
-	 * pivot table which contains the primary key values from both models.
-	 *
-	 * Note that this method requires that the primary key value is not null.
-	 *
-	 * @param string $name the case sensitive name of the relationship
-	 * @param ActiveRecord $model the model to be linked with the current one.
-	 * @param array $extraColumns additional column values to be saved into the pivot table.
-	 * This parameter is only meaningful for a relationship involving a pivot table
-	 * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.)
-	 * @throws InvalidCallException if the method is unable to link two models.
-	 */
-	public function link($name, $model, $extraColumns = [])
-	{
-		$relation = $this->getRelation($name);
-
-		if ($relation->via !== null) {
-			if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
-				throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
-			}
-			if (is_array($relation->via)) {
-				/** @var ActiveRelation $viaRelation */
-				list($viaName, $viaRelation) = $relation->via;
-				$viaClass = $viaRelation->modelClass;
-				// unset $viaName so that it can be reloaded to reflect the change
-				unset($this->_related[$viaName]);
-			} else {
-				$viaRelation = $relation->via;
-				$viaTable = reset($relation->via->from);
-			}
-			$columns = [];
-			foreach ($viaRelation->link as $a => $b) {
-				$columns[$a] = $this->$b;
-			}
-			foreach ($relation->link as $a => $b) {
-				$columns[$b] = $model->$a;
-			}
-			foreach ($extraColumns as $k => $v) {
-				$columns[$k] = $v;
-			}
-			if (is_array($relation->via)) {
-				/** @var $viaClass ActiveRecord */
-				/** @var $record ActiveRecord */
-				$record = new $viaClass();
-				foreach($columns as $column => $value) {
-					$record->$column = $value;
-				}
-				$record->insert(false);
-			} else {
-				/** @var $viaTable string */
-				static::getDb()->createCommand()
-					->insert($viaTable, $columns)->execute();
-			}
-		} else {
-			$p1 = $model->isPrimaryKey(array_keys($relation->link));
-			$p2 = $this->isPrimaryKey(array_values($relation->link));
-			if ($p1 && $p2) {
-				if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
-					throw new InvalidCallException('Unable to link models: both models are newly created.');
-				} elseif ($this->getIsNewRecord()) {
-					$this->bindModels(array_flip($relation->link), $this, $model);
-				} else {
-					$this->bindModels($relation->link, $model, $this);
-				}
-			} elseif ($p1) {
-				$this->bindModels(array_flip($relation->link), $this, $model);
-			} elseif ($p2) {
-				$this->bindModels($relation->link, $model, $this);
-			} else {
-				throw new InvalidCallException('Unable to link models: the link does not involve any primary key.');
-			}
-		}
-
-		// update lazily loaded related objects
-		if (!$relation->multiple) {
-			$this->_related[$name] = $model;
-		} elseif (isset($this->_related[$name])) {
-			if ($relation->indexBy !== null) {
-				$indexBy = $relation->indexBy;
-				$this->_related[$name][$model->$indexBy] = $model;
-			} else {
-				$this->_related[$name][] = $model;
-			}
-		}
-	}
-
-	/**
-	 * Destroys the relationship between two models.
-	 *
-	 * The model with the foreign key of the relationship will be deleted if `$delete` is true.
-	 * Otherwise, the foreign key will be set null and the model will be saved without validation.
-	 *
-	 * @param string $name the case sensitive name of the relationship.
-	 * @param ActiveRecord $model the model to be unlinked from the current one.
-	 * @param boolean $delete whether to delete the model that contains the foreign key.
-	 * If false, the model's foreign key will be set null and saved.
-	 * If true, the model containing the foreign key will be deleted.
-	 * @throws InvalidCallException if the models cannot be unlinked
-	 */
-	public function unlink($name, $model, $delete = false)
-	{
-		$relation = $this->getRelation($name);
-
-		if ($relation->via !== null) {
-			if (is_array($relation->via)) {
-				/** @var ActiveRelation $viaRelation */
-				list($viaName, $viaRelation) = $relation->via;
-				$viaClass = $viaRelation->modelClass;
-				unset($this->_related[$viaName]);
-			} else {
-				$viaRelation = $relation->via;
-				$viaTable = reset($relation->via->from);
-			}
-			$columns = [];
-			foreach ($viaRelation->link as $a => $b) {
-				$columns[$a] = $this->$b;
-			}
-			foreach ($relation->link as $a => $b) {
-				$columns[$b] = $model->$a;
-			}
-			if (is_array($relation->via)) {
-				/** @var $viaClass ActiveRecord */
-				if ($delete) {
-					$viaClass::deleteAll($columns);
-				} else {
-					$nulls = [];
-					foreach (array_keys($columns) as $a) {
-						$nulls[$a] = null;
-					}
-					$viaClass::updateAll($nulls, $columns);
-				}
-			} else {
-				/** @var $viaTable string */
-				$command = static::getDb()->createCommand();
-				if ($delete) {
-					$command->delete($viaTable, $columns)->execute();
-				} else {
-					$nulls = [];
-					foreach (array_keys($columns) as $a) {
-						$nulls[$a] = null;
-					}
-					$command->update($viaTable, $nulls, $columns)->execute();
-				}
-			}
-		} else {
-			$p1 = $model->isPrimaryKey(array_keys($relation->link));
-			$p2 = $this->isPrimaryKey(array_values($relation->link));
-			if ($p1 && $p2 || $p2) {
-				foreach ($relation->link as $a => $b) {
-					$model->$a = null;
-				}
-				$delete ? $model->delete() : $model->save(false);
-			} elseif ($p1) {
-				foreach ($relation->link as $b) {
-					$this->$b = null;
-				}
-				$delete ? $this->delete() : $this->save(false);
-			} else {
-				throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
-			}
-		}
-
-		if (!$relation->multiple) {
-			unset($this->_related[$name]);
-		} elseif (isset($this->_related[$name])) {
-			/** @var ActiveRecord $b */
-			foreach ($this->_related[$name] as $a => $b) {
-				if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
-					unset($this->_related[$name][$a]);
-				}
-			}
-		}
-	}
-
-	/**
-	 * @param array $link
-	 * @param ActiveRecord $foreignModel
-	 * @param ActiveRecord $primaryModel
-	 * @throws InvalidCallException
-	 */
-	private function bindModels($link, $foreignModel, $primaryModel)
-	{
-		foreach ($link as $fk => $pk) {
-			$value = $primaryModel->$pk;
-			if ($value === null) {
-				throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
-			}
-			$foreignModel->$fk = $value;
-		}
-		$foreignModel->save(false);
-	}
-
-	/**
-	 * Returns a value indicating whether the given set of attributes represents the primary key for this model
-	 * @param array $keys the set of attributes to check
-	 * @return boolean whether the given set of attributes represents the primary key for this model
-	 */
-	public static function isPrimaryKey($keys)
-	{
-		$pks = static::primaryKey();
-		foreach ($keys as $key) {
-			if (!in_array($key, $pks, true)) {
-				return false;
-			}
-		}
-		return count($keys) === count($pks);
-	}
-
-	/**
 	 * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
 	 * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
 	 * @return boolean whether the specified operation is transactional in the current [[scenario]].
diff --git a/framework/yii/db/ActiveRecordInterface.php b/framework/yii/db/ActiveRecordInterface.php
new file mode 100644
index 0000000..4965a26
--- /dev/null
+++ b/framework/yii/db/ActiveRecordInterface.php
@@ -0,0 +1,279 @@
+<?php
+/**
+ * 
+ * 
+ * @author Carsten Brandt <mail@cebe.cc>
+ */
+
+namespace yii\db;
+
+/**
+ * ActiveRecordInterface
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @since 2.0
+ */
+interface ActiveRecordInterface
+{
+	/**
+	 * Returns the primary key **name(s)** for this AR class.
+	 *
+	 * Note that an array should be returned even when the record only has a single primary key.
+	 *
+	 * For the primary key **value** see [[getPrimaryKey()]] instead.
+	 *
+	 * @return string[] the primary key name(s) for this AR class.
+	 */
+	public static function primaryKey();
+
+	/**
+	 * Returns the list of all attribute names of the record.
+	 * @return array list of attribute names.
+	 */
+	public function attributes();
+
+	/**
+	 * Returns the named attribute value.
+	 * If this record is the result of a query and the attribute is not loaded,
+	 * null will be returned.
+	 * @param string $name the attribute name
+	 * @return mixed the attribute value. Null if the attribute is not set or does not exist.
+	 * @see hasAttribute()
+	 */
+	public function getAttribute($name);
+
+	/**
+	 * Sets the named attribute value.
+	 * @param string $name the attribute name.
+	 * @param mixed $value the attribute value.
+	 * @see hasAttribute()
+	 */
+	public function setAttribute($name, $value);
+
+	/**
+	 * Returns a value indicating whether the record has an attribute with the specified name.
+	 * @param string $name the name of the attribute
+	 * @return boolean whether the record has an attribute with the specified name.
+	 */
+	public function hasAttribute($name);
+
+	/**
+	 * Returns the primary key value(s).
+	 * @param boolean $asArray whether to return the primary key value as an array. If true,
+	 * the return value will be an array with attribute names as keys and attribute values as values.
+	 * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
+	 * @return mixed the primary key value. An array (attribute name => attribute value) is returned if the primary key
+	 * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+	 * the key value is null).
+	 */
+	public function getPrimaryKey($asArray = false);
+
+	/**
+	 * Creates an [[ActiveQueryInterface|ActiveQuery]] instance for query purpose.
+	 *
+	 * This method is usually ment to be used like this:
+	 *
+	 * ```php
+	 * Customer::find(1); // find one customer by primary key
+	 * Customer::find()->all(); // find all customers
+	 * ```
+	 *
+	 * @param mixed $q the query parameter. This can be one of the followings:
+	 *
+	 *  - a scalar value (integer or string): query by a single primary key value and return the
+	 *    corresponding record.
+	 *  - an array of name-value pairs: query by a set of attribute values and return a single record matching all of them.
+	 *  - null (not specified): return a new [[ActiveQuery]] object for further query purpose.
+	 *
+	 * @return ActiveQueryInterface|static|null When `$q` is null, a new [[ActiveQuery]] instance
+	 * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
+	 * returned (null will be returned if there is no matching).
+	 */
+	public static function find($q = null);
+
+	/**
+	 * Creates an [[ActiveQueryInterface|ActiveQuery]] instance.
+	 * This method is called by [[find()]] to start a SELECT query.
+	 * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+	 * written for querying `Customer` purpose.)
+	 * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
+	 */
+	public static function createQuery();
+
+	/**
+	 * Updates records using the provided attribute values and conditions.
+	 * For example, to change the status to be 1 for all customers whose status is 2:
+	 *
+	 * ~~~
+	 * Customer::updateAll(['status' => 1], ['status' => '2']);
+	 * ~~~
+	 *
+	 * @param array $attributes attribute values (name-value pairs) to be saved for the record.
+	 * Unlike [[update()]] these are not going to be validated.
+	 * @param array $condition the condition that matches the records that should get updated.
+	 * Please refer to [[QueryInterface::where()]] on how to specify this parameter.
+	 * An empty condition will match all records.
+	 * @return integer the number of rows updated
+	 */
+	public static function updateAll($attributes, $condition = null);
+
+	/**
+	 * Deletes records using the provided conditions.
+	 * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+	 *
+	 * For example, to delete all customers whose status is 3:
+	 *
+	 * ~~~
+	 * Customer::deleteAll([status = 3]);
+	 * ~~~
+	 *
+	 * @param array $condition the condition that matches the records that should get deleted.
+	 * Please refer to [[QueryInterface::where()]] on how to specify this parameter.
+	 * An empty condition will match all records.
+	 * @return integer the number of rows deleted
+	 */
+	public static function deleteAll($condition = null);
+
+	/**
+	 * Saves the current record.
+	 *
+	 * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
+	 * when [[isNewRecord]] is false.
+	 *
+	 * For example, to save a customer record:
+	 *
+	 * ~~~
+	 * $customer = new Customer; // or $customer = Customer::find($id);
+	 * $customer->name = $name;
+	 * $customer->email = $email;
+	 * $customer->save();
+	 * ~~~
+	 *
+	 * @param boolean $runValidation whether to perform validation before saving the record.
+	 * If the validation fails, the record will not be saved to database. `false` will be returned
+	 * in this case.
+	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
+	 * meaning all attributes that are loaded from DB will be saved.
+	 * @return boolean whether the saving succeeds
+	 */
+	public function save($runValidation = true, $attributes = null);
+
+	/**
+	 * Inserts the record into the database using the attribute values of this record.
+	 *
+	 * Usage example:
+	 *
+	 * ```php
+	 * $customer = new Customer;
+	 * $customer->name = $name;
+	 * $customer->email = $email;
+	 * $customer->insert();
+	 * ```
+	 *
+	 * @param boolean $runValidation whether to perform validation before saving the record.
+	 * If the validation fails, the record will not be inserted into the database.
+	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
+	 * meaning all attributes that are loaded from DB will be saved.
+	 * @return boolean whether the attributes are valid and the record is inserted successfully.
+	 */
+	public function insert($runValidation = true, $attributes = null);
+
+	/**
+	 * Saves the changes to this active record into the database.
+	 *
+	 * Usage example:
+	 *
+	 * ```php
+	 * $customer = Customer::find($id);
+	 * $customer->name = $name;
+	 * $customer->email = $email;
+	 * $customer->update();
+	 * ```
+	 *
+	 * @param boolean $runValidation whether to perform validation before saving the record.
+	 * If the validation fails, the record will not be inserted into the database.
+	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
+	 * meaning all attributes that are loaded from DB will be saved.
+	 * @return integer|boolean the number of rows affected, or false if validation fails
+	 * or updating process is stopped for other reasons.
+	 * Note that it is possible that the number of rows affected is 0, even though the
+	 * update execution is successful.
+	 */
+	public function update($runValidation = true, $attributes = null);
+
+	/**
+	 * Deletes the record from the database.
+	 *
+	 * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+	 * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful.
+	 */
+	public function delete();
+
+	/**
+	 * Returns a value indicating whether the current record is new (not saved in the database).
+	 * @return boolean whether the record is new and should be inserted when calling [[save()]].
+	 */
+	public function getIsNewRecord();
+
+	/**
+	 * Returns a value indicating whether the given active record is the same as the current one.
+	 * Two [[isNewRecord|new]] records are considered to be not equal.
+	 * @param static $record record to compare to
+	 * @return boolean whether the two active records refer to the same row in the same database table.
+	 */
+	public function equals($record);
+
+	/**
+	 * Creates an [[ActiveRelationInterface|ActiveRelation]] instance.
+	 * This method is called by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to
+	 * create a relation instance.
+	 * You may override this method to return a customized relation.
+	 * @param array $config the configuration passed to the ActiveRelation class.
+	 * @return ActiveRelation the newly created [[ActiveRelation]] instance.
+	 */
+	public static function createActiveRelation($config = []);
+
+	/**
+	 * Returns the relation object with the specified name.
+	 * A relation is defined by a getter method which returns an [[ActiveRelationInterface|ActiveRelation]] object.
+	 * It can be declared in either the ActiveRecord class itself or one of its behaviors.
+	 * @param string $name the relation name
+	 * @return ActiveRelation the relation object
+	 */
+	public function getRelation($name);
+
+	/**
+	 * Establishes the relationship between two records.
+	 *
+	 * The relationship is established by setting the foreign key value(s) in one record
+	 * to be the corresponding primary key value(s) in the other record.
+	 * The record with the foreign key will be saved into database without performing validation.
+	 *
+	 * If the relationship involves a pivot table, a new row will be inserted into the
+	 * pivot table which contains the primary key values from both records.
+	 *
+	 * This method requires that the primary key value is not null.
+	 *
+	 * @param string $name the case sensitive name of the relationship.
+	 * @param static $model the record to be linked with the current one.
+	 * @param array $extraColumns additional column values to be saved into the pivot table.
+	 * This parameter is only meaningful for a relationship involving a pivot table
+	 * (i.e., a relation set with `[[ActiveRelationInterface::via()]]`.)
+	 */
+	public function link($name, $model, $extraColumns = []);
+
+	/**
+	 * Destroys the relationship between two records.
+	 *
+	 * The record with the foreign key of the relationship will be deleted if `$delete` is true.
+	 * Otherwise, the foreign key will be set null and the record will be saved without validation.
+	 *
+	 * @param string $name the case sensitive name of the relationship.
+	 * @param static $model the model to be unlinked from the current one.
+	 * @param boolean $delete whether to delete the model that contains the foreign key.
+	 * If false, the model's foreign key will be set null and saved.
+	 * If true, the model containing the foreign key will be deleted.
+	 */
+	public function unlink($name, $model, $delete = false);
+}
\ No newline at end of file
diff --git a/framework/yii/db/ActiveRelationTrait.php b/framework/yii/db/ActiveRelationTrait.php
index 2960196..832bb62 100644
--- a/framework/yii/db/ActiveRelationTrait.php
+++ b/framework/yii/db/ActiveRelationTrait.php
@@ -104,7 +104,7 @@ trait ActiveRelationTrait
 		if (count($primaryModels) === 1 && !$this->multiple) {
 			$model = $this->one();
 			foreach ($primaryModels as $i => $primaryModel) {
-				if ($primaryModel instanceof ActiveRecord) {
+				if ($primaryModel instanceof ActiveRecordInterface) {
 					$primaryModel->populateRelation($name, $model);
 				} else {
 					$primaryModels[$i][$name] = $model;
@@ -123,7 +123,7 @@ trait ActiveRelationTrait
 			foreach ($primaryModels as $i => $primaryModel) {
 				$key = $this->getModelKey($primaryModel, $link);
 				$value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null);
-				if ($primaryModel instanceof ActiveRecord) {
+				if ($primaryModel instanceof ActiveRecordInterface) {
 					$primaryModel->populateRelation($name, $value);
 				} else {
 					$primaryModels[$i][$name] = $value;
diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/yii/db/BaseActiveRecord.php
new file mode 100644
index 0000000..00360c4
--- /dev/null
+++ b/framework/yii/db/BaseActiveRecord.php
@@ -0,0 +1,1229 @@
+<?php
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\db;
+
+use yii\base\InvalidConfigException;
+use yii\base\Model;
+use yii\base\InvalidParamException;
+use yii\base\ModelEvent;
+use yii\base\NotSupportedException;
+use yii\base\UnknownMethodException;
+use yii\base\InvalidCallException;
+use yii\helpers\StringHelper;
+use yii\helpers\Inflector;
+
+/**
+ * ActiveRecord is the base class for classes representing relational data in terms of objects.
+ *
+ * @include @yii/db/ActiveRecord.md
+ *
+ * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is
+ * read-only.
+ * @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]].
+ * @property array $oldAttributes The old attribute values (name-value pairs).
+ * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is
+ * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key
+ * value is null). This property is read-only.
+ * @property array $populatedRelations An array of relation data indexed by relation names. This property is
+ * read-only.
+ * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if
+ * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null).
+ * This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @since 2.0
+ */
+abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
+{
+	/**
+	 * @event Event an event that is triggered when the record is initialized via [[init()]].
+	 */
+	const EVENT_INIT = 'init';
+	/**
+	 * @event Event an event that is triggered after the record is created and populated with query result.
+	 */
+	const EVENT_AFTER_FIND = 'afterFind';
+	/**
+	 * @event ModelEvent an event that is triggered before inserting a record.
+	 * You may set [[ModelEvent::isValid]] to be false to stop the insertion.
+	 */
+	const EVENT_BEFORE_INSERT = 'beforeInsert';
+	/**
+	 * @event Event an event that is triggered after a record is inserted.
+	 */
+	const EVENT_AFTER_INSERT = 'afterInsert';
+	/**
+	 * @event ModelEvent an event that is triggered before updating a record.
+	 * You may set [[ModelEvent::isValid]] to be false to stop the update.
+	 */
+	const EVENT_BEFORE_UPDATE = 'beforeUpdate';
+	/**
+	 * @event Event an event that is triggered after a record is updated.
+	 */
+	const EVENT_AFTER_UPDATE = 'afterUpdate';
+	/**
+	 * @event ModelEvent an event that is triggered before deleting a record.
+	 * You may set [[ModelEvent::isValid]] to be false to stop the deletion.
+	 */
+	const EVENT_BEFORE_DELETE = 'beforeDelete';
+	/**
+	 * @event Event an event that is triggered after a record is deleted.
+	 */
+	const EVENT_AFTER_DELETE = 'afterDelete';
+
+	/**
+	 * @var array attribute values indexed by attribute names
+	 */
+	private $_attributes = [];
+	/**
+	 * @var array old attribute values indexed by attribute names.
+	 */
+	private $_oldAttributes;
+	/**
+	 * @var array related models indexed by the relation names
+	 */
+	private $_related = [];
+
+
+	/**
+	 * Creates an [[ActiveQuery]] instance for query purpose.
+	 *
+	 * @include @yii/db/ActiveRecord-find.md
+	 *
+	 * @param mixed $q the query parameter. This can be one of the followings:
+	 *
+	 *  - a scalar value (integer or string): query by a single primary key value and return the
+	 *    corresponding record.
+	 *  - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
+	 *  - null: return a new [[ActiveQuery]] object for further query purpose.
+	 *
+	 * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance
+	 * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
+	 * returned (null will be returned if there is no matching).
+	 * @throws InvalidConfigException if the AR class does not have a primary key
+	 * @see createQuery()
+	 */
+	public static function find($q = null)
+	{
+		$query = static::createQuery();
+		if (is_array($q)) {
+			return $query->where($q)->one();
+		} elseif ($q !== null) {
+			// query by primary key
+			$primaryKey = static::primaryKey();
+			if (isset($primaryKey[0])) {
+				return $query->where([$primaryKey[0] => $q])->one();
+			} else {
+				throw new InvalidConfigException(get_called_class() . ' must have a primary key.');
+			}
+		}
+		return $query;
+	}
+
+	/**
+	 * Updates the whole table using the provided attribute values and conditions.
+	 * For example, to change the status to be 1 for all customers whose status is 2:
+	 *
+	 * ~~~
+	 * Customer::updateAll(['status' => 1], 'status = 2');
+	 * ~~~
+	 *
+	 * @param array $attributes attribute values (name-value pairs) to be saved into the table
+	 * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+	 * Please refer to [[Query::where()]] on how to specify this parameter.
+	 * @param array $params the parameters (name => value) to be bound to the query.
+	 * @return integer the number of rows updated
+	 */
+	public static function updateAll($attributes, $condition = '')
+	{
+		throw new NotSupportedException(__METHOD__ . ' is not supported.');
+	}
+
+	/**
+	 * Updates the whole table using the provided counter changes and conditions.
+	 * For example, to increment all customers' age by 1,
+	 *
+	 * ~~~
+	 * Customer::updateAllCounters(['age' => 1]);
+	 * ~~~
+	 *
+	 * @param array $counters the counters to be updated (attribute name => increment value).
+	 * Use negative values if you want to decrement the counters.
+	 * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+	 * Please refer to [[Query::where()]] on how to specify this parameter.
+	 * @param array $params the parameters (name => value) to be bound to the query.
+	 * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method.
+	 * @return integer the number of rows updated
+	 */
+	public static function updateAllCounters($counters, $condition = '')
+	{
+		throw new NotSupportedException(__METHOD__ . ' is not supported.');
+	}
+
+	/**
+	 * Deletes rows in the table using the provided conditions.
+	 * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+	 *
+	 * For example, to delete all customers whose status is 3:
+	 *
+	 * ~~~
+	 * Customer::deleteAll('status = 3');
+	 * ~~~
+	 *
+	 * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
+	 * Please refer to [[Query::where()]] on how to specify this parameter.
+	 * @param array $params the parameters (name => value) to be bound to the query.
+	 * @return integer the number of rows deleted
+	 */
+	public static function deleteAll($condition = '', $params = [])
+	{
+		throw new NotSupportedException(__METHOD__ . ' is not supported.');
+	}
+
+	/**
+	 * Returns the name of the column that stores the lock version for implementing optimistic locking.
+	 *
+	 * Optimistic locking allows multiple users to access the same record for edits and avoids
+	 * potential conflicts. In case when a user attempts to save the record upon some staled data
+	 * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
+	 * and the update or deletion is skipped.
+	 *
+	 * Optimistic locking is only supported by [[update()]] and [[delete()]].
+	 *
+	 * To use Optimistic locking:
+	 *
+	 * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
+	 *    Override this method to return the name of this column.
+	 * 2. In the Web form that collects the user input, add a hidden field that stores
+	 *    the lock version of the recording being updated.
+	 * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
+	 *    and implement necessary business logic (e.g. merging the changes, prompting stated data)
+	 *    to resolve the conflict.
+	 *
+	 * @return string the column name that stores the lock version of a table row.
+	 * If null is returned (default implemented), optimistic locking will not be supported.
+	 */
+	public function optimisticLock()
+	{
+		return null;
+	}
+
+	/**
+	 * PHP getter magic method.
+	 * This method is overridden so that attributes and related objects can be accessed like properties.
+	 * @param string $name property name
+	 * @return mixed property value
+	 * @see getAttribute()
+	 */
+	public function __get($name)
+	{
+		if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
+			return $this->_attributes[$name];
+		} elseif ($this->hasAttribute($name)) {
+			return null;
+		} else {
+			if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
+				return $this->_related[$name];
+			}
+			$value = parent::__get($name);
+			if ($value instanceof ActiveRelationInterface) {
+				return $this->_related[$name] = $value->multiple ? $value->all() : $value->one();
+			} else {
+				return $value;
+			}
+		}
+	}
+
+	/**
+	 * PHP setter magic method.
+	 * This method is overridden so that AR attributes can be accessed like properties.
+	 * @param string $name property name
+	 * @param mixed $value property value
+	 */
+	public function __set($name, $value)
+	{
+		if ($this->hasAttribute($name)) {
+			$this->_attributes[$name] = $value;
+		} else {
+			parent::__set($name, $value);
+		}
+	}
+
+	/**
+	 * Checks if a property value is null.
+	 * This method overrides the parent implementation by checking if the named attribute is null or not.
+	 * @param string $name the property name or the event name
+	 * @return boolean whether the property value is null
+	 */
+	public function __isset($name)
+	{
+		try {
+			return $this->__get($name) !== null;
+		} catch (\Exception $e) {
+			return false;
+		}
+	}
+
+	/**
+	 * Sets a component property to be null.
+	 * This method overrides the parent implementation by clearing
+	 * the specified attribute value.
+	 * @param string $name the property name or the event name
+	 */
+	public function __unset($name)
+	{
+		if ($this->hasAttribute($name)) {
+			unset($this->_attributes[$name]);
+		} else {
+			if (isset($this->_related[$name])) {
+				unset($this->_related[$name]);
+			} else {
+				parent::__unset($name);
+			}
+		}
+	}
+
+	/**
+	 * Declares a `has-one` relation.
+	 * The declaration is returned in terms of an [[ActiveRelation]] instance
+	 * through which the related record can be queried and retrieved back.
+	 *
+	 * A `has-one` relation means that there is at most one related record matching
+	 * the criteria set by this relation, e.g., a customer has one country.
+	 *
+	 * For example, to declare the `country` relation for `Customer` class, we can write
+	 * the following code in the `Customer` class:
+	 *
+	 * ~~~
+	 * public function getCountry()
+	 * {
+	 *     return $this->hasOne(Country::className(), ['id' => 'country_id']);
+	 * }
+	 * ~~~
+	 *
+	 * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
+	 * in the related class `Country`, while the 'country_id' value refers to an attribute name
+	 * in the current AR class.
+	 *
+	 * Call methods declared in [[ActiveRelation]] to further customize the relation.
+	 *
+	 * @param string $class the class name of the related record
+	 * @param array $link the primary-foreign key constraint. The keys of the array refer to
+	 * the attributes of the record associated with the `$class` model, while the values of the
+	 * array refer to the corresponding attributes in **this** AR class.
+	 * @return ActiveRelationInterface the relation object.
+	 */
+	public function hasOne($class, $link)
+	{
+		/** @var ActiveRecord $class */
+		return $class::createActiveRelation([
+			'modelClass' => $class,
+			'primaryModel' => $this,
+			'link' => $link,
+			'multiple' => false,
+		]);
+	}
+
+	/**
+	 * Declares a `has-many` relation.
+	 * The declaration is returned in terms of an [[ActiveRelation]] instance
+	 * through which the related record can be queried and retrieved back.
+	 *
+	 * A `has-many` relation means that there are multiple related records matching
+	 * the criteria set by this relation, e.g., a customer has many orders.
+	 *
+	 * For example, to declare the `orders` relation for `Customer` class, we can write
+	 * the following code in the `Customer` class:
+	 *
+	 * ~~~
+	 * public function getOrders()
+	 * {
+	 *     return $this->hasMany(Order::className(), ['customer_id' => 'id']);
+	 * }
+	 * ~~~
+	 *
+	 * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
+	 * an attribute name in the related class `Order`, while the 'id' value refers to
+	 * an attribute name in the current AR class.
+	 *
+	 * @param string $class the class name of the related record
+	 * @param array $link the primary-foreign key constraint. The keys of the array refer to
+	 * the attributes of the record associated with the `$class` model, while the values of the
+	 * array refer to the corresponding attributes in **this** AR class.
+	 * @return ActiveRelationInterface the relation object.
+	 */
+	public function hasMany($class, $link)
+	{
+		/** @var ActiveRecord $class */
+		return $class::createActiveRelation([
+			'modelClass' => $class,
+			'primaryModel' => $this,
+			'link' => $link,
+			'multiple' => true,
+		]);
+	}
+
+	/**
+	 * Populates the named relation with the related records.
+	 * Note that this method does not check if the relation exists or not.
+	 * @param string $name the relation name (case-sensitive)
+	 * @param ActiveRecord|array|null the related records to be populated into the relation.
+	 */
+	public function populateRelation($name, $records)
+	{
+		$this->_related[$name] = $records;
+	}
+
+	/**
+	 * Check whether the named relation has been populated with records.
+	 * @param string $name the relation name (case-sensitive)
+	 * @return bool whether relation has been populated with records.
+	 */
+	public function isRelationPopulated($name)
+	{
+		return array_key_exists($name, $this->_related);
+	}
+
+	/**
+	 * Returns all populated relations.
+	 * @return array an array of relation data indexed by relation names.
+	 */
+	public function getPopulatedRelations()
+	{
+		return $this->_related;
+	}
+
+	/**
+	 * Returns a value indicating whether the model has an attribute with the specified name.
+	 * @param string $name the name of the attribute
+	 * @return boolean whether the model has an attribute with the specified name.
+	 */
+	public function hasAttribute($name)
+	{
+		return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
+	}
+
+	/**
+	 * Returns the named attribute value.
+	 * If this record is the result of a query and the attribute is not loaded,
+	 * null will be returned.
+	 * @param string $name the attribute name
+	 * @return mixed the attribute value. Null if the attribute is not set or does not exist.
+	 * @see hasAttribute()
+	 */
+	public function getAttribute($name)
+	{
+		return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
+	}
+
+	/**
+	 * Sets the named attribute value.
+	 * @param string $name the attribute name
+	 * @param mixed $value the attribute value.
+	 * @throws InvalidParamException if the named attribute does not exist.
+	 * @see hasAttribute()
+	 */
+	public function setAttribute($name, $value)
+	{
+		if ($this->hasAttribute($name)) {
+			$this->_attributes[$name] = $value;
+		} else {
+			throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
+		}
+	}
+
+	/**
+	 * Returns the old attribute values.
+	 * @return array the old attribute values (name-value pairs)
+	 */
+	public function getOldAttributes()
+	{
+		return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
+	}
+
+	/**
+	 * Sets the old attribute values.
+	 * All existing old attribute values will be discarded.
+	 * @param array $values old attribute values to be set.
+	 */
+	public function setOldAttributes($values)
+	{
+		$this->_oldAttributes = $values;
+	}
+
+	/**
+	 * Returns the old value of the named attribute.
+	 * If this record is the result of a query and the attribute is not loaded,
+	 * null will be returned.
+	 * @param string $name the attribute name
+	 * @return mixed the old attribute value. Null if the attribute is not loaded before
+	 * or does not exist.
+	 * @see hasAttribute()
+	 */
+	public function getOldAttribute($name)
+	{
+		return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
+	}
+
+	/**
+	 * Sets the old value of the named attribute.
+	 * @param string $name the attribute name
+	 * @param mixed $value the old attribute value.
+	 * @throws InvalidParamException if the named attribute does not exist.
+	 * @see hasAttribute()
+	 */
+	public function setOldAttribute($name, $value)
+	{
+		if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
+			$this->_oldAttributes[$name] = $value;
+		} else {
+			throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
+		}
+	}
+
+	/**
+	 * Returns a value indicating whether the named attribute has been changed.
+	 * @param string $name the name of the attribute
+	 * @return boolean whether the attribute has been changed
+	 */
+	public function isAttributeChanged($name)
+	{
+		if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
+			return $this->_attributes[$name] !== $this->_oldAttributes[$name];
+		} else {
+			return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
+		}
+	}
+
+	/**
+	 * Returns the attribute values that have been modified since they are loaded or saved most recently.
+	 * @param string[]|null $names the names of the attributes whose values may be returned if they are
+	 * changed recently. If null, [[attributes()]] will be used.
+	 * @return array the changed attribute values (name-value pairs)
+	 */
+	public function getDirtyAttributes($names = null)
+	{
+		if ($names === null) {
+			$names = $this->attributes();
+		}
+		$names = array_flip($names);
+		$attributes = [];
+		if ($this->_oldAttributes === null) {
+			foreach ($this->_attributes as $name => $value) {
+				if (isset($names[$name])) {
+					$attributes[$name] = $value;
+				}
+			}
+		} else {
+			foreach ($this->_attributes as $name => $value) {
+				if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
+					$attributes[$name] = $value;
+				}
+			}
+		}
+		return $attributes;
+	}
+
+	/**
+	 * Saves the current record.
+	 *
+	 * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
+	 * when [[isNewRecord]] is false.
+	 *
+	 * For example, to save a customer record:
+	 *
+	 * ~~~
+	 * $customer = new Customer;  // or $customer = Customer::find($id);
+	 * $customer->name = $name;
+	 * $customer->email = $email;
+	 * $customer->save();
+	 * ~~~
+	 *
+	 *
+	 * @param boolean $runValidation whether to perform validation before saving the record.
+	 * If the validation fails, the record will not be saved to database.
+	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
+	 * meaning all attributes that are loaded from DB will be saved.
+	 * @return boolean whether the saving succeeds
+	 */
+	public function save($runValidation = true, $attributes = null)
+	{
+		if ($this->getIsNewRecord()) {
+			return $this->insert($runValidation, $attributes);
+		} else {
+			return $this->update($runValidation, $attributes) !== false;
+		}
+	}
+
+	/**
+	 * Saves the changes to this active record into the associated database table.
+	 *
+	 * This method performs the following steps in order:
+	 *
+	 * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+	 *    fails, it will skip the rest of the steps;
+	 * 2. call [[afterValidate()]] when `$runValidation` is true.
+	 * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+	 *    rest of the steps;
+	 * 4. save the record into database. If this fails, it will skip the rest of the steps;
+	 * 5. call [[afterSave()]];
+	 *
+	 * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+	 * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
+	 * will be raised by the corresponding methods.
+	 *
+	 * Only the [[changedAttributes|changed attribute values]] will be saved into database.
+	 *
+	 * For example, to update a customer record:
+	 *
+	 * ~~~
+	 * $customer = Customer::find($id);
+	 * $customer->name = $name;
+	 * $customer->email = $email;
+	 * $customer->update();
+	 * ~~~
+	 *
+	 * Note that it is possible the update does not affect any row in the table.
+	 * In this case, this method will return 0. For this reason, you should use the following
+	 * code to check if update() is successful or not:
+	 *
+	 * ~~~
+	 * if ($this->update() !== false) {
+	 *     // update successful
+	 * } else {
+	 *     // update failed
+	 * }
+	 * ~~~
+	 *
+	 * @param boolean $runValidation whether to perform validation before saving the record.
+	 * If the validation fails, the record will not be inserted into the database.
+	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
+	 * meaning all attributes that are loaded from DB will be saved.
+	 * @return integer|boolean the number of rows affected, or false if validation fails
+	 * or [[beforeSave()]] stops the updating process.
+	 * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+	 * being updated is outdated.
+	 * @throws \Exception in case update failed.
+	 */
+	public function update($runValidation = true, $attributes = null)
+	{
+		if ($runValidation && !$this->validate($attributes)) {
+			return false;
+		}
+		return $this->updateInternal($attributes);
+	}
+
+	/**
+	 * @see CActiveRecord::update()
+	 * @throws StaleObjectException
+	 */
+	protected function updateInternal($attributes = null)
+	{
+		if (!$this->beforeSave(false)) {
+			return false;
+		}
+		$values = $this->getDirtyAttributes($attributes);
+		if (empty($values)) {
+			$this->afterSave(false);
+			return 0;
+		}
+		$condition = $this->getOldPrimaryKey(true);
+		$lock = $this->optimisticLock();
+		if ($lock !== null) {
+			if (!isset($values[$lock])) {
+				$values[$lock] = $this->$lock + 1;
+			}
+			$condition[$lock] = $this->$lock;
+		}
+		// We do not check the return value of updateAll() because it's possible
+		// that the UPDATE statement doesn't change anything and thus returns 0.
+		$rows = $this->updateAll($values, $condition);
+
+		if ($lock !== null && !$rows) {
+			throw new StaleObjectException('The object being updated is outdated.');
+		}
+
+		foreach ($values as $name => $value) {
+			$this->_oldAttributes[$name] = $this->_attributes[$name];
+		}
+		$this->afterSave(false);
+		return $rows;
+	}
+
+	/**
+	 * Updates one or several counter columns for the current AR object.
+	 * Note that this method differs from [[updateAllCounters()]] in that it only
+	 * saves counters for the current AR object.
+	 *
+	 * An example usage is as follows:
+	 *
+	 * ~~~
+	 * $post = Post::find($id);
+	 * $post->updateCounters(['view_count' => 1]);
+	 * ~~~
+	 *
+	 * @param array $counters the counters to be updated (attribute name => increment value)
+	 * Use negative values if you want to decrement the counters.
+	 * @return boolean whether the saving is successful
+	 * @see updateAllCounters()
+	 */
+	public function updateCounters($counters)
+	{
+		if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
+			foreach ($counters as $name => $value) {
+				$this->_attributes[$name] += $value;
+				$this->_oldAttributes[$name] = $this->_attributes[$name];
+			}
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Deletes the table row corresponding to this active record.
+	 *
+	 * This method performs the following steps in order:
+	 *
+	 * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
+	 *    rest of the steps;
+	 * 2. delete the record from the database;
+	 * 3. call [[afterDelete()]].
+	 *
+	 * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
+	 * will be raised by the corresponding methods.
+	 *
+	 * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+	 * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
+	 * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+	 * being deleted is outdated.
+	 * @throws \Exception in case delete failed.
+	 */
+	public function delete()
+	{
+		$result = false;
+		if ($this->beforeDelete()) {
+			// we do not check the return value of deleteAll() because it's possible
+			// the record is already deleted in the database and thus the method will return 0
+			$condition = $this->getOldPrimaryKey(true);
+			$lock = $this->optimisticLock();
+			if ($lock !== null) {
+				$condition[$lock] = $this->$lock;
+			}
+			$result = $this->deleteAll($condition);
+			if ($lock !== null && !$result) {
+				throw new StaleObjectException('The object being deleted is outdated.');
+			}
+			$this->_oldAttributes = null;
+			$this->afterDelete();
+		}
+		return $result;
+	}
+
+	/**
+	 * Returns a value indicating whether the current record is new.
+	 * @return boolean whether the record is new and should be inserted when calling [[save()]].
+	 */
+	public function getIsNewRecord()
+	{
+		return $this->_oldAttributes === null;
+	}
+
+	/**
+	 * Sets the value indicating whether the record is new.
+	 * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
+	 * @see getIsNewRecord()
+	 */
+	public function setIsNewRecord($value)
+	{
+		$this->_oldAttributes = $value ? null : $this->_attributes;
+	}
+
+	/**
+	 * Initializes the object.
+	 * This method is called at the end of the constructor.
+	 * The default implementation will trigger an [[EVENT_INIT]] event.
+	 * If you override this method, make sure you call the parent implementation at the end
+	 * to ensure triggering of the event.
+	 */
+	public function init()
+	{
+		parent::init();
+		$this->trigger(self::EVENT_INIT);
+	}
+
+	/**
+	 * This method is called when the AR object is created and populated with the query result.
+	 * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
+	 * When overriding this method, make sure you call the parent implementation to ensure the
+	 * event is triggered.
+	 */
+	public function afterFind()
+	{
+		$this->trigger(self::EVENT_AFTER_FIND);
+	}
+
+	/**
+	 * This method is called at the beginning of inserting or updating a record.
+	 * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
+	 * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
+	 * When overriding this method, make sure you call the parent implementation like the following:
+	 *
+	 * ~~~
+	 * public function beforeSave($insert)
+	 * {
+	 *     if (parent::beforeSave($insert)) {
+	 *         // ...custom code here...
+	 *         return true;
+	 *     } else {
+	 *         return false;
+	 *     }
+	 * }
+	 * ~~~
+	 *
+	 * @param boolean $insert whether this method called while inserting a record.
+	 * If false, it means the method is called while updating a record.
+	 * @return boolean whether the insertion or updating should continue.
+	 * If false, the insertion or updating will be cancelled.
+	 */
+	public function beforeSave($insert)
+	{
+		$event = new ModelEvent;
+		$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
+		return $event->isValid;
+	}
+
+	/**
+	 * This method is called at the end of inserting or updating a record.
+	 * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
+	 * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
+	 * When overriding this method, make sure you call the parent implementation so that
+	 * the event is triggered.
+	 * @param boolean $insert whether this method called while inserting a record.
+	 * If false, it means the method is called while updating a record.
+	 */
+	public function afterSave($insert)
+	{
+		$this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
+	}
+
+	/**
+	 * This method is invoked before deleting a record.
+	 * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
+	 * When overriding this method, make sure you call the parent implementation like the following:
+	 *
+	 * ~~~
+	 * public function beforeDelete()
+	 * {
+	 *     if (parent::beforeDelete()) {
+	 *         // ...custom code here...
+	 *         return true;
+	 *     } else {
+	 *         return false;
+	 *     }
+	 * }
+	 * ~~~
+	 *
+	 * @return boolean whether the record should be deleted. Defaults to true.
+	 */
+	public function beforeDelete()
+	{
+		$event = new ModelEvent;
+		$this->trigger(self::EVENT_BEFORE_DELETE, $event);
+		return $event->isValid;
+	}
+
+	/**
+	 * This method is invoked after deleting a record.
+	 * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
+	 * You may override this method to do postprocessing after the record is deleted.
+	 * Make sure you call the parent implementation so that the event is raised properly.
+	 */
+	public function afterDelete()
+	{
+		$this->trigger(self::EVENT_AFTER_DELETE);
+	}
+
+	/**
+	 * Repopulates this active record with the latest data.
+	 * @return boolean whether the row still exists in the database. If true, the latest data
+	 * will be populated to this active record. Otherwise, this record will remain unchanged.
+	 */
+	public function refresh()
+	{
+		$record = $this->find($this->getPrimaryKey(true));
+		if ($record === null) {
+			return false;
+		}
+		foreach ($this->attributes() as $name) {
+			$this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
+		}
+		$this->_oldAttributes = $this->_attributes;
+		$this->_related = [];
+		return true;
+	}
+
+	/**
+	 * Returns a value indicating whether the given active record is the same as the current one.
+	 * The comparison is made by comparing the table names and the primary key values of the two active records.
+	 * If one of the records [[isNewRecord|is new]] they are also considered not equal.
+	 * @param ActiveRecord $record record to compare to
+	 * @return boolean whether the two active records refer to the same row in the same database table.
+	 */
+	public function equals($record)
+	{
+		if ($this->isNewRecord || $record->isNewRecord) {
+			return false;
+		}
+		return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey();
+	}
+
+	/**
+	 * Returns the primary key value(s).
+	 * @param boolean $asArray whether to return the primary key value as an array. If true,
+	 * the return value will be an array with column names as keys and column values as values.
+	 * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
+	 * @property mixed The primary key value. An array (column name => column value) is returned if
+	 * the primary key is composite. A string is returned otherwise (null will be returned if
+	 * the key value is null).
+	 * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
+	 * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+	 * the key value is null).
+	 */
+	public function getPrimaryKey($asArray = false)
+	{
+		$keys = $this->primaryKey();
+		if (count($keys) === 1 && !$asArray) {
+			return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
+		} else {
+			$values = [];
+			foreach ($keys as $name) {
+				$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
+			}
+			return $values;
+		}
+	}
+
+	/**
+	 * Returns the old primary key value(s).
+	 * This refers to the primary key value that is populated into the record
+	 * after executing a find method (e.g. find(), findAll()).
+	 * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
+	 * @param boolean $asArray whether to return the primary key value as an array. If true,
+	 * the return value will be an array with column name as key and column value as value.
+	 * If this is false (default), a scalar value will be returned for non-composite primary key.
+	 * @property mixed The old primary key value. An array (column name => column value) is
+	 * returned if the primary key is composite. A string is returned otherwise (null will be
+	 * returned if the key value is null).
+	 * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
+	 * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+	 * the key value is null).
+	 */
+	public function getOldPrimaryKey($asArray = false)
+	{
+		$keys = $this->primaryKey();
+		if (count($keys) === 1 && !$asArray) {
+			return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
+		} else {
+			$values = [];
+			foreach ($keys as $name) {
+				$values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
+			}
+			return $values;
+		}
+	}
+
+	/**
+	 * Creates an active record object using a row of data.
+	 * This method is called by [[ActiveQuery]] to populate the query results
+	 * into Active Records. It is not meant to be used to create new records.
+	 * @param array $row attribute values (name => value)
+	 * @return ActiveRecord the newly created active record.
+	 */
+	public static function create($row)
+	{
+		$record = static::instantiate($row);
+		$columns = array_flip($record->attributes());
+		foreach ($row as $name => $value) {
+			if (isset($columns[$name])) {
+				$record->_attributes[$name] = $value;
+			} else {
+				$record->$name = $value;
+			}
+		}
+		$record->_oldAttributes = $record->_attributes;
+		$record->afterFind();
+		return $record;
+	}
+
+	/**
+	 * Creates an active record instance.
+	 * This method is called by [[create()]].
+	 * You may override this method if the instance being created
+	 * depends on the row data to be populated into the record.
+	 * For example, by creating a record based on the value of a column,
+	 * you may implement the so-called single-table inheritance mapping.
+	 * @param array $row row data to be populated into the record.
+	 * @return ActiveRecord the newly created active record
+	 */
+	public static function instantiate($row)
+	{
+		return new static;
+	}
+
+	/**
+	 * Returns whether there is an element at the specified offset.
+	 * This method is required by the interface ArrayAccess.
+	 * @param mixed $offset the offset to check on
+	 * @return boolean whether there is an element at the specified offset.
+	 */
+	public function offsetExists($offset)
+	{
+		return $this->__isset($offset);
+	}
+
+	/**
+	 * Returns the relation object with the specified name.
+	 * A relation is defined by a getter method which returns an [[ActiveRelation]] object.
+	 * It can be declared in either the Active Record class itself or one of its behaviors.
+	 * @param string $name the relation name
+	 * @return ActiveRelation the relation object
+	 * @throws InvalidParamException if the named relation does not exist.
+	 */
+	public function getRelation($name)
+	{
+		$getter = 'get' . $name;
+		try {
+			$relation = $this->$getter();
+			if ($relation instanceof ActiveRelationInterface) {
+				return $relation;
+			} else {
+				throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
+			}
+		} catch (UnknownMethodException $e) {
+			throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
+		}
+	}
+
+	/**
+	 * Establishes the relationship between two models.
+	 *
+	 * The relationship is established by setting the foreign key value(s) in one model
+	 * to be the corresponding primary key value(s) in the other model.
+	 * The model with the foreign key will be saved into database without performing validation.
+	 *
+	 * If the relationship involves a pivot table, a new row will be inserted into the
+	 * pivot table which contains the primary key values from both models.
+	 *
+	 * Note that this method requires that the primary key value is not null.
+	 *
+	 * @param string $name the case sensitive name of the relationship
+	 * @param ActiveRecord $model the model to be linked with the current one.
+	 * @param array $extraColumns additional column values to be saved into the pivot table.
+	 * This parameter is only meaningful for a relationship involving a pivot table
+	 * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.)
+	 * @throws InvalidCallException if the method is unable to link two models.
+	 */
+	public function link($name, $model, $extraColumns = [])
+	{
+		$relation = $this->getRelation($name);
+
+		if ($relation->via !== null) {
+			if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
+				throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
+			}
+			if (is_array($relation->via)) {
+				/** @var ActiveRelation $viaRelation */
+				list($viaName, $viaRelation) = $relation->via;
+				$viaClass = $viaRelation->modelClass;
+				// unset $viaName so that it can be reloaded to reflect the change
+				unset($this->_related[$viaName]);
+			} else {
+				$viaRelation = $relation->via;
+				$viaTable = reset($relation->via->from);
+			}
+			$columns = [];
+			foreach ($viaRelation->link as $a => $b) {
+				$columns[$a] = $this->$b;
+			}
+			foreach ($relation->link as $a => $b) {
+				$columns[$b] = $model->$a;
+			}
+			foreach ($extraColumns as $k => $v) {
+				$columns[$k] = $v;
+			}
+			if (is_array($relation->via)) {
+				/** @var $viaClass ActiveRecord */
+				/** @var $record ActiveRecord */
+				$record = new $viaClass();
+				foreach($columns as $column => $value) {
+					$record->$column = $value;
+				}
+				$record->insert(false);
+			} else {
+				/** @var $viaTable string */
+				static::getDb()->createCommand()
+					->insert($viaTable, $columns)->execute();
+			}
+		} else {
+			$p1 = $model->isPrimaryKey(array_keys($relation->link));
+			$p2 = $this->isPrimaryKey(array_values($relation->link));
+			if ($p1 && $p2) {
+				if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
+					throw new InvalidCallException('Unable to link models: both models are newly created.');
+				} elseif ($this->getIsNewRecord()) {
+					$this->bindModels(array_flip($relation->link), $this, $model);
+				} else {
+					$this->bindModels($relation->link, $model, $this);
+				}
+			} elseif ($p1) {
+				$this->bindModels(array_flip($relation->link), $this, $model);
+			} elseif ($p2) {
+				$this->bindModels($relation->link, $model, $this);
+			} else {
+				throw new InvalidCallException('Unable to link models: the link does not involve any primary key.');
+			}
+		}
+
+		// update lazily loaded related objects
+		if (!$relation->multiple) {
+			$this->_related[$name] = $model;
+		} elseif (isset($this->_related[$name])) {
+			if ($relation->indexBy !== null) {
+				$indexBy = $relation->indexBy;
+				$this->_related[$name][$model->$indexBy] = $model;
+			} else {
+				$this->_related[$name][] = $model;
+			}
+		}
+	}
+
+	/**
+	 * Destroys the relationship between two models.
+	 *
+	 * The model with the foreign key of the relationship will be deleted if `$delete` is true.
+	 * Otherwise, the foreign key will be set null and the model will be saved without validation.
+	 *
+	 * @param string $name the case sensitive name of the relationship.
+	 * @param ActiveRecord $model the model to be unlinked from the current one.
+	 * @param boolean $delete whether to delete the model that contains the foreign key.
+	 * If false, the model's foreign key will be set null and saved.
+	 * If true, the model containing the foreign key will be deleted.
+	 * @throws InvalidCallException if the models cannot be unlinked
+	 */
+	public function unlink($name, $model, $delete = false)
+	{
+		$relation = $this->getRelation($name);
+
+		if ($relation->via !== null) {
+			if (is_array($relation->via)) {
+				/** @var ActiveRelation $viaRelation */
+				list($viaName, $viaRelation) = $relation->via;
+				$viaClass = $viaRelation->modelClass;
+				unset($this->_related[$viaName]);
+			} else {
+				$viaRelation = $relation->via;
+				$viaTable = reset($relation->via->from);
+			}
+			$columns = [];
+			foreach ($viaRelation->link as $a => $b) {
+				$columns[$a] = $this->$b;
+			}
+			foreach ($relation->link as $a => $b) {
+				$columns[$b] = $model->$a;
+			}
+			if (is_array($relation->via)) {
+				/** @var $viaClass ActiveRecord */
+				if ($delete) {
+					$viaClass::deleteAll($columns);
+				} else {
+					$nulls = [];
+					foreach (array_keys($columns) as $a) {
+						$nulls[$a] = null;
+					}
+					$viaClass::updateAll($nulls, $columns);
+				}
+			} else {
+				/** @var $viaTable string */
+				$command = static::getDb()->createCommand();
+				if ($delete) {
+					$command->delete($viaTable, $columns)->execute();
+				} else {
+					$nulls = [];
+					foreach (array_keys($columns) as $a) {
+						$nulls[$a] = null;
+					}
+					$command->update($viaTable, $nulls, $columns)->execute();
+				}
+			}
+		} else {
+			$p1 = $model->isPrimaryKey(array_keys($relation->link));
+			$p2 = $this->isPrimaryKey(array_values($relation->link));
+			if ($p1 && $p2 || $p2) {
+				foreach ($relation->link as $a => $b) {
+					$model->$a = null;
+				}
+				$delete ? $model->delete() : $model->save(false);
+			} elseif ($p1) {
+				foreach ($relation->link as $b) {
+					$this->$b = null;
+				}
+				$delete ? $this->delete() : $this->save(false);
+			} else {
+				throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
+			}
+		}
+
+		if (!$relation->multiple) {
+			unset($this->_related[$name]);
+		} elseif (isset($this->_related[$name])) {
+			/** @var ActiveRecord $b */
+			foreach ($this->_related[$name] as $a => $b) {
+				if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
+					unset($this->_related[$name][$a]);
+				}
+			}
+		}
+	}
+
+	/**
+	 * @param array $link
+	 * @param ActiveRecord $foreignModel
+	 * @param ActiveRecord $primaryModel
+	 * @throws InvalidCallException
+	 */
+	private function bindModels($link, $foreignModel, $primaryModel)
+	{
+		foreach ($link as $fk => $pk) {
+			$value = $primaryModel->$pk;
+			if ($value === null) {
+				throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
+			}
+			$foreignModel->$fk = $value;
+		}
+		$foreignModel->save(false);
+	}
+
+	/**
+	 * Returns a value indicating whether the given set of attributes represents the primary key for this model
+	 * @param array $keys the set of attributes to check
+	 * @return boolean whether the given set of attributes represents the primary key for this model
+	 */
+	public static function isPrimaryKey($keys)
+	{
+		$pks = static::primaryKey();
+		foreach ($keys as $key) {
+			if (!in_array($key, $pks, true)) {
+				return false;
+			}
+		}
+		return count($keys) === count($pks);
+	}
+}
diff --git a/framework/yii/db/Migration.php b/framework/yii/db/Migration.php
index 37fdf3f..79a5abc 100644
--- a/framework/yii/db/Migration.php
+++ b/framework/yii/db/Migration.php
@@ -312,7 +312,7 @@ class Migration extends \yii\base\Component
 	 * Builds and executes a SQL statement for changing the definition of a column.
 	 * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
 	 * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
-	 * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
+	 * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
 	 * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
 	 * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
 	 */
diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php
index 20d13a8..870c66d 100644
--- a/framework/yii/db/Query.php
+++ b/framework/yii/db/Query.php
@@ -266,6 +266,10 @@ class Query extends Component implements QueryInterface
 	 * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
 	 * The method will automatically quote the column names unless a column contains some parenthesis
 	 * (which means the column contains a DB expression).
+	 *
+	 * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should
+	 * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
+	 * 
 	 * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
 	 * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
 	 * @return static the query object itself
diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php
index 45bd4a2..9acf91f 100644
--- a/framework/yii/db/cubrid/QueryBuilder.php
+++ b/framework/yii/db/cubrid/QueryBuilder.php
@@ -69,7 +69,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function buildLimit($limit, $offset)
 	{
diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php
index 9cb321f..e9481a4 100644
--- a/framework/yii/db/mysql/QueryBuilder.php
+++ b/framework/yii/db/mysql/QueryBuilder.php
@@ -142,7 +142,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function buildLimit($limit, $offset)
 	{
diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php
index ae049e7..bddc436 100644
--- a/framework/yii/db/sqlite/QueryBuilder.php
+++ b/framework/yii/db/sqlite/QueryBuilder.php
@@ -255,7 +255,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function buildLimit($limit, $offset)
 	{
diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php
index 2ee1db2..707d411 100644
--- a/framework/yii/grid/ActionColumn.php
+++ b/framework/yii/grid/ActionColumn.php
@@ -32,27 +32,27 @@ class ActionColumn extends Column
 	protected function initDefaultButtons()
 	{
 		if (!isset($this->buttons['view'])) {
-			$this->buttons['view'] = function ($model, $column) {
+			$this->buttons['view'] = function ($model, $key, $index, $column) {
 				/** @var ActionColumn $column */
-				$url = $column->createUrl($model, 'view');
+				$url = $column->createUrl($model, $key, $index, 'view');
 				return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', $url, [
 					'title' => Yii::t('yii', 'View'),
 				]);
 			};
 		}
 		if (!isset($this->buttons['update'])) {
-			$this->buttons['update'] = function ($model, $column) {
+			$this->buttons['update'] = function ($model, $key, $index, $column) {
 				/** @var ActionColumn $column */
-				$url = $column->createUrl($model, 'update');
+				$url = $column->createUrl($model, $key, $index, 'update');
 				return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url, [
 					'title' => Yii::t('yii', 'Update'),
 				]);
 			};
 		}
 		if (!isset($this->buttons['delete'])) {
-			$this->buttons['delete'] = function ($model, $column) {
+			$this->buttons['delete'] = function ($model, $key, $index, $column) {
 				/** @var ActionColumn $column */
-				$url = $column->createUrl($model, 'delete');
+				$url = $column->createUrl($model, $key, $index, 'delete');
 				return Html::a('<span class="glyphicon glyphicon-trash"></span>', $url, [
 					'title' => Yii::t('yii', 'Delete'),
 					'data-confirm' => Yii::t('yii', 'Are you sure to delete this item?'),
@@ -64,34 +64,30 @@ class ActionColumn extends Column
 
 	/**
 	 * @param \yii\db\ActiveRecord $model
+	 * @param mixed $key the key associated with the data model
+	 * @param integer $index
 	 * @param string $action
 	 * @return string
 	 */
-	public function createUrl($model, $action)
+	public function createUrl($model, $key, $index, $action)
 	{
 		if ($this->urlCreator instanceof Closure) {
-			return call_user_func($this->urlCreator, $model, $action);
+			return call_user_func($this->urlCreator, $model, $key, $index, $action);
 		} else {
-			$params = $model->getPrimaryKey(true);
-			if (count($params) === 1) {
-				$params = ['id' => reset($params)];
-			}
+			$params = is_array($key) ? $key : ['id' => $key];
 			return Yii::$app->controller->createUrl($action, $params);
 		}
 	}
 
 	/**
-	 * Renders the data cell content.
-	 * @param mixed $model the data model
-	 * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]].
-	 * @return string the rendering result
+	 * @inheritdoc
 	 */
-	protected function renderDataCellContent($model, $index)
+	protected function renderDataCellContent($model, $key, $index)
 	{
-		return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model) {
+		return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $key, $index) {
 			$name = $matches[1];
 			if (isset($this->buttons[$name])) {
-				return call_user_func($this->buttons[$name], $model, $this);
+				return call_user_func($this->buttons[$name], $model, $key, $index, $this);
 			} else {
 				return '';
 			}
diff --git a/framework/yii/grid/CheckboxColumn.php b/framework/yii/grid/CheckboxColumn.php
index d029648..6970d4b 100644
--- a/framework/yii/grid/CheckboxColumn.php
+++ b/framework/yii/grid/CheckboxColumn.php
@@ -44,7 +44,7 @@ class CheckboxColumn extends Column
 
 	/**
 	 * Renders the header cell content.
-	 * The default implementation simply renders {@link header}.
+	 * The default implementation simply renders [[header]].
 	 * This method may be overridden to customize the rendering of the header cell.
 	 * @return string the rendering result
 	 */
@@ -67,15 +67,12 @@ class CheckboxColumn extends Column
 	}
 
 	/**
-	 * Renders the data cell content.
-	 * @param mixed $model the data model
-	 * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]].
-	 * @return string the rendering result
+	 * @inheritdoc
 	 */
-	protected function renderDataCellContent($model, $index)
+	protected function renderDataCellContent($model, $key, $index)
 	{
 		if ($this->checkboxOptions instanceof Closure) {
-			$options = call_user_func($this->checkboxOptions, $model, $index, $this);
+			$options = call_user_func($this->checkboxOptions, $model, $key, $index, $this);
 		} else {
 			$options = $this->checkboxOptions;
 		}
diff --git a/framework/yii/grid/Column.php b/framework/yii/grid/Column.php
index ec0c886..5cc4c42 100644
--- a/framework/yii/grid/Column.php
+++ b/framework/yii/grid/Column.php
@@ -71,17 +71,18 @@ class Column extends Object
 	/**
 	 * Renders a data cell.
 	 * @param mixed $model the data model being rendered
+	 * @param mixed $key the key associated with the data model
 	 * @param integer $index the zero-based index of the data item among the item array returned by [[dataProvider]].
 	 * @return string the rendering result
 	 */
-	public function renderDataCell($model, $index)
+	public function renderDataCell($model, $key, $index)
 	{
 		if ($this->contentOptions instanceof Closure) {
-			$options = call_user_func($this->contentOptions, $model, $index, $this);
+			$options = call_user_func($this->contentOptions, $model, $key, $index, $this);
 		} else {
 			$options = $this->contentOptions;
 		}
-		return Html::tag('td', $this->renderDataCellContent($model, $index), $options);
+		return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options);
 	}
 
 	/**
@@ -94,7 +95,7 @@ class Column extends Object
 
 	/**
 	 * Renders the header cell content.
-	 * The default implementation simply renders {@link header}.
+	 * The default implementation simply renders [[header]].
 	 * This method may be overridden to customize the rendering of the header cell.
 	 * @return string the rendering result
 	 */
@@ -105,7 +106,7 @@ class Column extends Object
 
 	/**
 	 * Renders the footer cell content.
-	 * The default implementation simply renders {@link footer}.
+	 * The default implementation simply renders [[footer]].
 	 * This method may be overridden to customize the rendering of the footer cell.
 	 * @return string the rendering result
 	 */
@@ -117,13 +118,14 @@ class Column extends Object
 	/**
 	 * Renders the data cell content.
 	 * @param mixed $model the data model
+	 * @param mixed $key the key associated with the data model
 	 * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]].
 	 * @return string the rendering result
 	 */
-	protected function renderDataCellContent($model, $index)
+	protected function renderDataCellContent($model, $key, $index)
 	{
 		if ($this->content !== null) {
-			return call_user_func($this->content, $model, $index, $this);
+			return call_user_func($this->content, $model, $key, $index, $this);
 		} else {
 			return $this->grid->emptyCell;
 		}
diff --git a/framework/yii/grid/DataColumn.php b/framework/yii/grid/DataColumn.php
index bd6eacb..d51af8f 100644
--- a/framework/yii/grid/DataColumn.php
+++ b/framework/yii/grid/DataColumn.php
@@ -133,14 +133,17 @@ class DataColumn extends Column
 		}
 	}
 
-	protected function renderDataCellContent($model, $index)
+	/**
+	 * @inheritdoc
+	 */
+	protected function renderDataCellContent($model, $key, $index)
 	{
 		if ($this->value !== null) {
 			$value = call_user_func($this->value, $model, $index, $this);
 		} elseif ($this->content === null && $this->attribute !== null) {
 			$value = ArrayHelper::getValue($model, $this->attribute);
 		} else {
-			return parent::renderDataCellContent($model, $index);
+			return parent::renderDataCellContent($model, $key, $index);
 		}
 		return $this->grid->formatter->format($value, $this->format);
 	}
diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php
index de99a18..35d89b2 100644
--- a/framework/yii/grid/GridView.php
+++ b/framework/yii/grid/GridView.php
@@ -162,7 +162,7 @@ class GridView extends BaseListView
 
 	/**
 	 * Initializes the grid view.
-	 * This method will initialize required property values and instantiate {@link columns} objects.
+	 * This method will initialize required property values and instantiate [[columns]] objects.
 	 */
 	public function init()
 	{
@@ -366,14 +366,14 @@ class GridView extends BaseListView
 		$cells = [];
 		/** @var Column $column */
 		foreach ($this->columns as $column) {
-			$cells[] = $column->renderDataCell($model, $index);
+			$cells[] = $column->renderDataCell($model, $key, $index);
 		}
 		if ($this->rowOptions instanceof Closure) {
 			$options = call_user_func($this->rowOptions, $model, $key, $index, $this);
 		} else {
 			$options = $this->rowOptions;
 		}
-		$options['data-key'] = $key;
+		$options['data-key'] = is_array($key) ? json_encode($key) : $key;
 		return Html::tag('tr', implode('', $cells), $options);
 	}
 
diff --git a/framework/yii/grid/SerialColumn.php b/framework/yii/grid/SerialColumn.php
index 6a875ae..8179ead 100644
--- a/framework/yii/grid/SerialColumn.php
+++ b/framework/yii/grid/SerialColumn.php
@@ -18,12 +18,9 @@ class SerialColumn extends Column
 	public $header = '#';
 
 	/**
-	 * Renders the data cell content.
-	 * @param mixed $model the data model
-	 * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]].
-	 * @return string the rendering result
+	 * @inheritdoc
 	 */
-	protected function renderDataCellContent($model, $index)
+	protected function renderDataCellContent($model, $key, $index)
 	{
 		$pagination = $this->grid->dataProvider->getPagination();
 		if ($pagination !== false) {
diff --git a/framework/yii/helpers/BaseConsole.php b/framework/yii/helpers/BaseConsole.php
index 480badf..92f7c46 100644
--- a/framework/yii/helpers/BaseConsole.php
+++ b/framework/yii/helpers/BaseConsole.php
@@ -142,7 +142,7 @@ class BaseConsole
 
 	/**
 	 * Saves the current cursor position by sending ANSI control code SCP to the terminal.
-	 * Position can then be restored with {@link restoreCursorPosition}.
+	 * Position can then be restored with [[restoreCursorPosition()]].
 	 */
 	public static function saveCursorPosition()
 	{
@@ -150,7 +150,7 @@ class BaseConsole
 	}
 
 	/**
-	 * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal.
+	 * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal.
 	 */
 	public static function restoreCursorPosition()
 	{
@@ -159,7 +159,7 @@ class BaseConsole
 
 	/**
 	 * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal.
-	 * Use {@link showCursor} to bring it back.
+	 * Use [[showCursor()]] to bring it back.
 	 * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit.
 	 */
 	public static function hideCursor()
@@ -168,7 +168,7 @@ class BaseConsole
 	}
 
 	/**
-	 * Will show a cursor again when it has been hidden by {@link hideCursor}  by sending ANSI DECTCEM code ?25h to the terminal.
+	 * Will show a cursor again when it has been hidden by [[hideCursor()]]  by sending ANSI DECTCEM code ?25h to the terminal.
 	 */
 	public static function showCursor()
 	{
diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php
index 9533b95..325cc8a 100644
--- a/framework/yii/helpers/BaseFileHelper.php
+++ b/framework/yii/helpers/BaseFileHelper.php
@@ -283,6 +283,11 @@ class BaseFileHelper
 				return $result;
 			}
 		}
+
+		if (empty($options['except']) && empty($options['only'])) {
+			return true;
+		}
+
 		$path = str_replace('\\', '/', $path);
 		if ($isDir = is_dir($path)) {
 			$path .= '/';
diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php
index 6f8e4e5..d435d71 100644
--- a/framework/yii/helpers/BaseHtml.php
+++ b/framework/yii/helpers/BaseHtml.php
@@ -739,6 +739,7 @@ class BaseHtml
 	 * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
 	 *   This option is ignored if `item` option is set.
 	 * - separator: string, the HTML code that separates items.
+	 * - itemOptions: array, the options for generating the radio button tag using [[checkbox()]].
 	 * - item: callable, a callback that can be used to customize the generation of the HTML code
 	 *   corresponding to a single item in $items. The signature of this callback must be:
 	 *
@@ -758,6 +759,7 @@ class BaseHtml
 		}
 
 		$formatter = isset($options['item']) ? $options['item'] : null;
+		$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : [];
 		$encode = !isset($options['encode']) || $options['encode'];
 		$lines = [];
 		$index = 0;
@@ -768,10 +770,10 @@ class BaseHtml
 			if ($formatter !== null) {
 				$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
 			} else {
-				$lines[] = static::checkbox($name, $checked, [
+				$lines[] = static::checkbox($name, $checked, array_merge($itemOptions, [
 					'value' => $value,
 					'label' => $encode ? static::encode($label) : $label,
-				]);
+				]));
 			}
 			$index++;
 		}
@@ -786,7 +788,7 @@ class BaseHtml
 		$separator = isset($options['separator']) ? $options['separator'] : "\n";
 
 		$tag = isset($options['tag']) ? $options['tag'] : 'div';
-		unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']);
+		unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']);
 
 		return $hidden . static::tag($tag, implode($separator, $lines), $options);
 	}
@@ -797,7 +799,7 @@ class BaseHtml
 	 * @param string $name the name attribute of each radio button.
 	 * @param string|array $selection the selected value(s).
 	 * @param array $items the data item used to generate the radio buttons.
-	 * The array keys are the labels, while the array values are the corresponding radio button values.
+	 * The array values are the labels, while the array keys are the corresponding radio button values.
 	 * @param array $options options (name => config) for the radio button list. The following options are supported:
 	 *
 	 * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
@@ -805,6 +807,7 @@ class BaseHtml
 	 * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
 	 *   This option is ignored if `item` option is set.
 	 * - separator: string, the HTML code that separates items.
+	 * - itemOptions: array, the options for generating the radio button tag using [[radio()]].
 	 * - item: callable, a callback that can be used to customize the generation of the HTML code
 	 *   corresponding to a single item in $items. The signature of this callback must be:
 	 *
@@ -821,6 +824,7 @@ class BaseHtml
 	{
 		$encode = !isset($options['encode']) || $options['encode'];
 		$formatter = isset($options['item']) ? $options['item'] : null;
+		$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : [];
 		$lines = [];
 		$index = 0;
 		foreach ($items as $value => $label) {
@@ -830,10 +834,10 @@ class BaseHtml
 			if ($formatter !== null) {
 				$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
 			} else {
-				$lines[] = static::radio($name, $checked, [
+				$lines[] = static::radio($name, $checked, array_merge($itemOptions, [
 					'value' => $value,
 					'label' => $encode ? static::encode($label) : $label,
-				]);
+				]));
 			}
 			$index++;
 		}
@@ -847,7 +851,7 @@ class BaseHtml
 		}
 
 		$tag = isset($options['tag']) ? $options['tag'] : 'div';
-		unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']);
+		unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']);
 
 		return $hidden . static::tag($tag, implode($separator, $lines), $options);
 	}
diff --git a/framework/yii/log/EmailTarget.php b/framework/yii/log/EmailTarget.php
index 95234d2..b924420 100644
--- a/framework/yii/log/EmailTarget.php
+++ b/framework/yii/log/EmailTarget.php
@@ -14,9 +14,8 @@ use yii\mail\MailerInterface;
 /**
  * EmailTarget sends selected log messages to the specified email addresses.
  *
- * The target email addresses may be specified via [[emails]] property.
- * Optionally, you may set the email [[subject]], [[sentFrom]] address and
- * additional [[headers]].
+ * You may configure the email to be sent by setting the [[message]] property, through which
+ * you can set the target email addresses, subject, etc.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
diff --git a/framework/yii/mail/BaseMessage.php b/framework/yii/mail/BaseMessage.php
index f95b4fb..d2594fc 100644
--- a/framework/yii/mail/BaseMessage.php
+++ b/framework/yii/mail/BaseMessage.php
@@ -18,15 +18,13 @@ use Yii;
  *
  * @see BaseMailer
  *
- * @property MailerInterface $mailer The mailer component. This property is read-only.
- *
  * @author Paul Klimov <klimov.paul@gmail.com>
  * @since 2.0
  */
 abstract class BaseMessage extends Object implements MessageInterface
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function send(MailerInterface $mailer = null)
 	{
diff --git a/framework/yii/rbac/PhpManager.php b/framework/yii/rbac/PhpManager.php
index 78e4d8c..3d02e4f 100644
--- a/framework/yii/rbac/PhpManager.php
+++ b/framework/yii/rbac/PhpManager.php
@@ -33,13 +33,12 @@ class PhpManager extends Manager
 {
 	/**
 	 * @var string the path of the PHP script that contains the authorization data.
-	 * If not set, it will be using 'protected/data/rbac.php' as the data file.
-	 * Make sure this file is writable by the Web server process if the authorization
-	 * needs to be changed.
+	 * This can be either a file path or a path alias to the file.
+	 * Make sure this file is writable by the Web server process if the authorization needs to be changed online.
 	 * @see loadFromFile()
 	 * @see saveToFile()
 	 */
-	public $authFile;
+	public $authFile = '@app/data/rbac.php';
 
 	private $_items = []; // itemName => item
 	private $_children = []; // itemName, childName => child
@@ -53,9 +52,7 @@ class PhpManager extends Manager
 	public function init()
 	{
 		parent::init();
-		if ($this->authFile === null) {
-			$this->authFile = Yii::getAlias('@app/data/rbac') . '.php';
-		}
+		$this->authFile = Yii::getAlias($this->authFile);
 		$this->load();
 	}
 
diff --git a/framework/yii/test/DbFixtureManager.php b/framework/yii/test/DbFixtureManager.php
index ed90284..23d25d4 100644
--- a/framework/yii/test/DbFixtureManager.php
+++ b/framework/yii/test/DbFixtureManager.php
@@ -11,6 +11,7 @@ use Yii;
 use yii\base\Component;
 use yii\base\InvalidConfigException;
 use yii\db\ActiveRecord;
+use yii\db\ActiveRecordInterface;
 use yii\db\Connection;
 
 /**
@@ -92,7 +93,7 @@ class DbFixtureManager extends Component
 		foreach ($fixtures as $name => $fixture) {
 			if (strpos($fixture, '\\') !== false) {
 				$model = new $fixture;
-				if ($model instanceof ActiveRecord) {
+				if ($model instanceof ActiveRecordInterface) {
 					$this->_modelClasses[$name] = $fixture;
 					$fixtures[$name] = $model->getTableSchema()->name;
 				} else {
diff --git a/framework/yii/validators/SafeValidator.php b/framework/yii/validators/SafeValidator.php
index 7cdc0a1..25da899 100644
--- a/framework/yii/validators/SafeValidator.php
+++ b/framework/yii/validators/SafeValidator.php
@@ -16,7 +16,7 @@ namespace yii\validators;
 class SafeValidator extends Validator
 {
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function validateAttribute($object, $attribute)
 	{
diff --git a/framework/yii/validators/UniqueValidator.php b/framework/yii/validators/UniqueValidator.php
index 053f795..d123aad 100644
--- a/framework/yii/validators/UniqueValidator.php
+++ b/framework/yii/validators/UniqueValidator.php
@@ -10,6 +10,7 @@ namespace yii\validators;
 use Yii;
 use yii\base\InvalidConfigException;
 use yii\db\ActiveRecord;
+use yii\db\ActiveRecordInterface;
 
 /**
  * UniqueValidator validates that the attribute value is unique in the corresponding database table.
@@ -67,7 +68,7 @@ class UniqueValidator extends Validator
 		$query = $className::find();
 		$query->where([$attributeName => $value]);
 
-		if (!$object instanceof ActiveRecord || $object->getIsNewRecord()) {
+		if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
 			// if current $object isn't in the database yet then it's OK just to call exists()
 			$exists = $query->exists();
 		} else {
diff --git a/framework/yii/validators/Validator.php b/framework/yii/validators/Validator.php
index f0602b6..2cd611b 100644
--- a/framework/yii/validators/Validator.php
+++ b/framework/yii/validators/Validator.php
@@ -159,7 +159,7 @@ abstract class Validator extends Component
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function init()
 	{
diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php
index 549f087..e755e80 100644
--- a/framework/yii/web/AccessControl.php
+++ b/framework/yii/web/AccessControl.php
@@ -131,14 +131,14 @@ class AccessControl extends ActionFilter
 	 * The default implementation will redirect the user to the login page if he is a guest;
 	 * if the user is already logged, a 403 HTTP exception will be thrown.
 	 * @param User $user the current user
-	 * @throws HttpException if the user is already logged in.
+	 * @throws AccessDeniedHttpException if the user is already logged in.
 	 */
 	protected function denyAccess($user)
 	{
 		if ($user->getIsGuest()) {
 			$user->loginRequired();
 		} else {
-			throw new HttpException(403, Yii::t('yii', 'You are not allowed to perform this action.'));
+			throw new AccessDeniedHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
 		}
 	}
 }
diff --git a/framework/yii/web/AccessDeniedHttpException.php b/framework/yii/web/AccessDeniedHttpException.php
new file mode 100644
index 0000000..d83700b
--- /dev/null
+++ b/framework/yii/web/AccessDeniedHttpException.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * AccessDeniedHttpException represents an "Access Denied" HTTP exception with status code 403.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AccessDeniedHttpException extends HttpException
+{
+	/**
+	 * Constructor.
+	 * @param string $message error message
+	 * @param integer $code error code
+	 * @param \Exception $previous The previous exception used for the exception chaining.
+	 */
+	public function __construct($message = null, $code = 0, \Exception $previous = null)
+	{
+		parent::__construct(403, $message, $code, $previous);
+	}
+}
diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php
index 13b5588..17c1411 100644
--- a/framework/yii/web/Application.php
+++ b/framework/yii/web/Application.php
@@ -58,7 +58,7 @@ class Application extends \yii\base\Application
 	 * Handles the specified request.
 	 * @param Request $request the request to be handled
 	 * @return Response the resulting response
-	 * @throws HttpException if the requested route is invalid
+	 * @throws NotFoundHttpException if the requested route is invalid
 	 */
 	public function handleRequest($request)
 	{
@@ -85,7 +85,7 @@ class Application extends \yii\base\Application
 				return $response;
 			}
 		} catch (InvalidRouteException $e) {
-			throw new HttpException(404, $e->getMessage(), $e->getCode(), $e);
+			throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e);
 		}
 	}
 
diff --git a/framework/yii/web/BadRequestHttpException.php b/framework/yii/web/BadRequestHttpException.php
new file mode 100644
index 0000000..3a6cfbb
--- /dev/null
+++ b/framework/yii/web/BadRequestHttpException.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * BadRequestHttpException represents a "Bad Request" HTTP exception with status code 400.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class BadRequestHttpException extends HttpException
+{
+	/**
+	 * Constructor.
+	 * @param string $message error message
+	 * @param integer $code error code
+	 * @param \Exception $previous The previous exception used for the exception chaining.
+	 */
+	public function __construct($message = null, $code = 0, \Exception $previous = null)
+	{
+		parent::__construct(400, $message, $code, $previous);
+	}
+}
diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php
index 1424d0c..0df48bd 100644
--- a/framework/yii/web/Controller.php
+++ b/framework/yii/web/Controller.php
@@ -62,7 +62,7 @@ class Controller extends \yii\base\Controller
 				} elseif (!is_array($params[$name])) {
 					$args[] = $actionParams[$name] = $params[$name];
 				} else {
-					throw new HttpException(400, Yii::t('yii', 'Invalid data received for parameter "{param}".', [
+					throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [
 						'param' => $name,
 					]));
 				}
@@ -75,7 +75,7 @@ class Controller extends \yii\base\Controller
 		}
 
 		if (!empty($missing)) {
-			throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', [
+			throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [
 				'params' => implode(', ', $missing),
 			]));
 		}
@@ -86,13 +86,13 @@ class Controller extends \yii\base\Controller
 	}
 
 	/**
-	 * {@inheritdoc}
+	 * @inheritdoc
 	 */
 	public function beforeAction($action)
 	{
 		if (parent::beforeAction($action)) {
 			if ($this->enableCsrfValidation && Yii::$app->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
-				throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.'));
+				throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
 			}
 			return true;
 		} else {
diff --git a/framework/yii/web/MethodNotAllowedHttpException.php b/framework/yii/web/MethodNotAllowedHttpException.php
new file mode 100644
index 0000000..d894f57
--- /dev/null
+++ b/framework/yii/web/MethodNotAllowedHttpException.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * MethodNotAllowedHttpException represents a "Method Not Allowed" HTTP exception with status code 405.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class MethodNotAllowedHttpException extends HttpException
+{
+	/**
+	 * Constructor.
+	 * @param string $message error message
+	 * @param integer $code error code
+	 * @param \Exception $previous The previous exception used for the exception chaining.
+	 */
+	public function __construct($message = null, $code = 0, \Exception $previous = null)
+	{
+		parent::__construct(405, $message, $code, $previous);
+	}
+}
diff --git a/framework/yii/web/NotFoundHttpException.php b/framework/yii/web/NotFoundHttpException.php
new file mode 100644
index 0000000..71f246d
--- /dev/null
+++ b/framework/yii/web/NotFoundHttpException.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * NotFoundHttpException represents a "Not Found" HTTP exception with status code 404.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class NotFoundHttpException extends HttpException
+{
+	/**
+	 * Constructor.
+	 * @param string $message error message
+	 * @param integer $code error code
+	 * @param \Exception $previous The previous exception used for the exception chaining.
+	 */
+	public function __construct($message = null, $code = 0, \Exception $previous = null)
+	{
+		parent::__construct(404, $message, $code, $previous);
+	}
+}
diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php
index 04bf0e3..9736043 100644
--- a/framework/yii/web/Request.php
+++ b/framework/yii/web/Request.php
@@ -139,7 +139,7 @@ class Request extends \yii\base\Request
 			$_GET = array_merge($_GET, $params);
 			return [$route, $_GET];
 		} else {
-			throw new HttpException(404, Yii::t('yii', 'Page not found.'));
+			throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
 		}
 	}
 
diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php
index 682d78e..b640756 100644
--- a/framework/yii/web/User.php
+++ b/framework/yii/web/User.php
@@ -331,7 +331,7 @@ class User extends Component
 			Yii::$app->getResponse()->redirect($this->loginUrl)->send();
 			exit();
 		} else {
-			throw new HttpException(403, Yii::t('yii', 'Login Required'));
+			throw new AccessDeniedHttpException(Yii::t('yii', 'Login Required'));
 		}
 	}
 
diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php
index e673bae..65d182b 100644
--- a/framework/yii/web/VerbFilter.php
+++ b/framework/yii/web/VerbFilter.php
@@ -100,7 +100,7 @@ class VerbFilter extends Behavior
 			$event->isValid = false;
 			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
 			Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
-			throw new HttpException(405, Yii::t('yii', 'Method Not Allowed. This url can only handle the following request methods: {methods}.', [
+			throw new MethodNotAllowedHttpException(Yii::t('yii', 'Method Not Allowed. This url can only handle the following request methods: {methods}.', [
 					'methods' => implode(', ', $allowed),
 			]));
 		}
diff --git a/framework/yii/widgets/FragmentCache.php b/framework/yii/widgets/FragmentCache.php
index 3005df5..57c4659 100644
--- a/framework/yii/widgets/FragmentCache.php
+++ b/framework/yii/widgets/FragmentCache.php
@@ -94,7 +94,7 @@ class FragmentCache extends Widget
 
 	/**
 	 * Marks the end of content to be cached.
-	 * Content displayed before this method call and after {@link init()}
+	 * Content displayed before this method call and after [[init()]]
 	 * will be captured and saved in cache.
 	 * This method does nothing if valid content is already found in cache.
 	 */
diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php
index ad13420..11d638c 100644
--- a/framework/yii/widgets/ListView.php
+++ b/framework/yii/widgets/ListView.php
@@ -88,7 +88,7 @@ class ListView extends BaseListView
 		$options = $this->itemOptions;
 		$tag = ArrayHelper::remove($options, 'tag', 'div');
 		if ($tag !== false) {
-			$options['data-key'] = $key;
+			$options['data-key'] = is_array($key) ? json_encode($key) : $key;
 			return Html::tag($tag, $content, $options);
 		} else {
 			return $content;
diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php
index 8bd2b12..465294f 100644
--- a/tests/unit/framework/console/controllers/MessageControllerTest.php
+++ b/tests/unit/framework/console/controllers/MessageControllerTest.php
@@ -108,7 +108,7 @@ class MessageControllerTest extends TestCase
 	}
 
 	/**
-	 * Creates message command config file at {@link configFileName}
+	 * Creates message command config file named as [[configFileName]].
 	 * @param array $config message command config.
 	 */
 	protected function composeConfigFile(array $config)