diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php
index 32c4e62..f584390 100644
--- a/build/controllers/PhpDocController.php
+++ b/build/controllers/PhpDocController.php
@@ -115,9 +115,9 @@ class PhpDocController extends Controller
     /**
      * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id), ['updateFiles']);
+        return array_merge(parent::options($actionId), ['updateFiles']);
     }
 
     protected function updateClassPropertyDocs($file, $className, $propertyDoc)
diff --git a/extensions/apidoc/commands/ApiController.php b/extensions/apidoc/commands/ApiController.php
index fb16078..9c9e286 100644
--- a/extensions/apidoc/commands/ApiController.php
+++ b/extensions/apidoc/commands/ApiController.php
@@ -159,8 +159,8 @@ class ApiController extends BaseController
     /**
      * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id), ['template', 'guide']);
+        return array_merge(parent::options($actionId), ['template', 'guide']);
     }
 }
diff --git a/extensions/apidoc/commands/GuideController.php b/extensions/apidoc/commands/GuideController.php
index 933e76b..754d49d 100644
--- a/extensions/apidoc/commands/GuideController.php
+++ b/extensions/apidoc/commands/GuideController.php
@@ -114,8 +114,8 @@ class GuideController extends BaseController
     /**
      * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id), ['apiDocs']);
+        return array_merge(parent::options($actionId), ['apiDocs']);
     }
 }
diff --git a/extensions/apidoc/components/BaseController.php b/extensions/apidoc/components/BaseController.php
index 9d700b2..e62ef37 100644
--- a/extensions/apidoc/components/BaseController.php
+++ b/extensions/apidoc/components/BaseController.php
@@ -126,8 +126,8 @@ abstract class BaseController extends Controller
     /**
      * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id), ['template', 'exclude']);
+        return array_merge(parent::options($actionId), ['template', 'exclude']);
     }
 }
diff --git a/extensions/apidoc/models/TypeDoc.php b/extensions/apidoc/models/TypeDoc.php
index 77a56de..c5be79c 100644
--- a/extensions/apidoc/models/TypeDoc.php
+++ b/extensions/apidoc/models/TypeDoc.php
@@ -86,8 +86,8 @@ class TypeDoc extends BaseDoc
     }
 
     /**
-     * @param  null        $visibility
-     * @param  null        $definedBy
+     * @param string|null $visibility
+     * @param string|null $definedBy
      * @return MethodDoc[]
      */
     private function getFilteredMethods($visibility = null, $definedBy = null)
diff --git a/extensions/apidoc/renderers/BaseRenderer.php b/extensions/apidoc/renderers/BaseRenderer.php
index 90ea56a..eb0ceb7 100644
--- a/extensions/apidoc/renderers/BaseRenderer.php
+++ b/extensions/apidoc/renderers/BaseRenderer.php
@@ -53,10 +53,10 @@ abstract class BaseRenderer extends Component
 
     /**
      * creates a link to a type (class, interface or trait)
-     * @param  ClassDoc|InterfaceDoc|TraitDoc|ClassDoc[]|InterfaceDoc[]|TraitDoc[] $types
-     * @param  string                                                              $title   a title to be used for the link TODO check whether [[yii\...|Class]] is supported
-     * @param  BaseDoc                                                             $context
-     * @param  array                                                               $options additional HTML attributes for the link.
+     * @param ClassDoc|InterfaceDoc|TraitDoc|ClassDoc[]|InterfaceDoc[]|TraitDoc[]|string|string[] $types
+     * @param string $title a title to be used for the link TODO check whether [[yii\...|Class]] is supported
+     * @param BaseDoc $context
+     * @param array $options additional HTML attributes for the link.
      * @return string
      */
     public function createTypeLink($types, $context = null, $title = null, $options = [])
diff --git a/extensions/apidoc/templates/bootstrap/layouts/api.php b/extensions/apidoc/templates/bootstrap/layouts/api.php
index b76d4ab..b5242d3 100644
--- a/extensions/apidoc/templates/bootstrap/layouts/api.php
+++ b/extensions/apidoc/templates/bootstrap/layouts/api.php
@@ -6,6 +6,7 @@ use yii\helpers\StringHelper;
 
 /**
  * @var yii\web\View $this
+ * @var array $types
  * @var string $content
  */
 
diff --git a/extensions/debug/panels/DbPanel.php b/extensions/debug/panels/DbPanel.php
index db20ae2..ba68391 100644
--- a/extensions/debug/panels/DbPanel.php
+++ b/extensions/debug/panels/DbPanel.php
@@ -86,7 +86,7 @@ class DbPanel extends Panel
     protected function calculateTimings()
     {
         if ($this->_timings === null) {
-            $this->_timings = Yii::$app->getLog()->calculateTimings($this->data['messages']);
+            $this->_timings = Yii::getLogger()->calculateTimings($this->data['messages']);
         }
 
         return $this->_timings;
diff --git a/extensions/debug/panels/ProfilingPanel.php b/extensions/debug/panels/ProfilingPanel.php
index 33f6984..635f4f8 100644
--- a/extensions/debug/panels/ProfilingPanel.php
+++ b/extensions/debug/panels/ProfilingPanel.php
@@ -85,7 +85,7 @@ class ProfilingPanel extends Panel
     {
         if ($this->_models === null) {
             $this->_models = [];
-            $timings = Yii::$app->getLog()->calculateTimings($this->data['messages']);
+            $timings = Yii::getLogger()->calculateTimings($this->data['messages']);
 
             foreach ($timings as $seq => $profileTiming) {
                 $this->_models[] = 	[
diff --git a/extensions/elasticsearch/Command.php b/extensions/elasticsearch/Command.php
index db2b62b..fa4defb 100644
--- a/extensions/elasticsearch/Command.php
+++ b/extensions/elasticsearch/Command.php
@@ -95,7 +95,7 @@ class Command extends Component
      */
     public function get($index, $type, $id, $options = [])
     {
-        return $this->db->get([$index, $type, $id], $options, null);
+        return $this->db->get([$index, $type, $id], $options);
     }
 
     /**
@@ -184,7 +184,7 @@ class Command extends Component
     {
         $body = $configuration !== null ? Json::encode($configuration) : null;
 
-        return $this->db->put([$index], $body);
+        return $this->db->put([$index], [], $body);
     }
 
     /**
@@ -381,7 +381,7 @@ class Command extends Component
             'mappings' => (object) $mappings,
         ]);
 
-        return $this->db->put(['_template', $name], $body);
+        return $this->db->put(['_template', $name], [], $body);
 
     }
 
diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php
index 30607ed..65523d4 100644
--- a/extensions/elasticsearch/QueryBuilder.php
+++ b/extensions/elasticsearch/QueryBuilder.php
@@ -203,6 +203,8 @@ class QueryBuilder extends \yii\base\Object
         return count($parts) === 1 ? $parts[0] : ['and' => $parts];
     }
 
+
+    // TODO implement these methods correctly
     private function buildNotCondition($operator, $operands, &$params)
     {
         if (count($operands) != 1) {
diff --git a/extensions/faker/FixtureController.php b/extensions/faker/FixtureController.php
index b7134a6..fbc3a2b 100644
--- a/extensions/faker/FixtureController.php
+++ b/extensions/faker/FixtureController.php
@@ -171,12 +171,11 @@ class FixtureController extends \yii\console\controllers\FixtureController
 
 
     /**
-     * Returns the names of the global options for this command.
-     * @return array the names of the global options for this command.
+     * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id), [
+        return array_merge(parent::options($actionId), [
             'templatePath', 'language', 'fixtureDataPath'
         ]);
     }
diff --git a/extensions/redis/ActiveQuery.php b/extensions/redis/ActiveQuery.php
index 7fc0a32..cdbb974 100644
--- a/extensions/redis/ActiveQuery.php
+++ b/extensions/redis/ActiveQuery.php
@@ -270,7 +270,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
 
     /**
      * Executes a script created by [[LuaScriptBuilder]]
-     * @param  Connection             $db         the database connection used to execute the query.
+     * @param  Connection|null        $db         the database connection used to execute the query.
      *                                            If this parameter is not given, the `db` application component will be used.
      * @param  string                 $type       the type of the script to generate
      * @param  string                 $columnName
diff --git a/extensions/redis/ActiveRecord.php b/extensions/redis/ActiveRecord.php
index 3244d6b..ae9a3d3 100644
--- a/extensions/redis/ActiveRecord.php
+++ b/extensions/redis/ActiveRecord.php
@@ -175,7 +175,7 @@ class ActiveRecord extends BaseActiveRecord
         }
         $db = static::getDb();
         $n = 0;
-        foreach (static::fetchPks($condition) as $pk) {
+        foreach (self::fetchPks($condition) as $pk) {
             $newPk = $pk;
             $pk = static::buildKey($pk);
             $key = static::keyPrefix() . ':a:' . $pk;
@@ -228,7 +228,7 @@ class ActiveRecord extends BaseActiveRecord
         }
         $db = static::getDb();
         $n = 0;
-        foreach (static::fetchPks($condition) as $pk) {
+        foreach (self::fetchPks($condition) as $pk) {
             $key = static::keyPrefix() . ':a:' . static::buildKey($pk);
             foreach ($counters as $attribute => $value) {
                 $db->executeCommand('HINCRBY', [$key, $attribute, $value]);
@@ -257,7 +257,7 @@ class ActiveRecord extends BaseActiveRecord
     {
         $db = static::getDb();
         $attributeKeys = [];
-        $pks = static::fetchPks($condition);
+        $pks = self::fetchPks($condition);
         $db->executeCommand('MULTI');
         foreach ($pks as $pk) {
             $pk = static::buildKey($pk);
diff --git a/extensions/redis/Connection.php b/extensions/redis/Connection.php
index 44bbe58..0622262 100644
--- a/extensions/redis/Connection.php
+++ b/extensions/redis/Connection.php
@@ -65,7 +65,6 @@ class Connection extends Component
      * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
      */
     public $dataTimeout = null;
-
     /**
      * @var array List of available redis commands http://redis.io/commands
      */
@@ -215,6 +214,7 @@ class Connection extends Component
      */
     private $_socket;
 
+
     /**
      * Closes the connection when this component is being serialized.
      * @return array
diff --git a/framework/BaseYii.php b/framework/BaseYii.php
index 5a1e817..bfe0240 100644
--- a/framework/BaseYii.php
+++ b/framework/BaseYii.php
@@ -348,6 +348,29 @@ class BaseYii
         }
     }
 
+    private static $_logger;
+    
+    /**
+     * @return Logger message logger
+     */
+    public static function getLogger()
+    {
+        if (self::$_logger !== null) {
+            return self::$_logger;
+        } else {
+            return self::$_logger = static::createObject('yii\log\Logger');
+        }
+    }
+
+    /**
+     * Sets the logger object.
+     * @param Logger $logger the logger object.
+     */
+    public static function setLogger($logger)
+    {
+        self::$_logger = $logger;
+    }
+
     /**
      * Logs a trace message.
      * Trace messages are logged mainly for development purpose to see
@@ -358,7 +381,7 @@ class BaseYii
     public static function trace($message, $category = 'application')
     {
         if (YII_DEBUG) {
-            static::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category);
+            static::getLogger()->log($message, Logger::LEVEL_TRACE, $category);
         }
     }
 
@@ -371,7 +394,7 @@ class BaseYii
      */
     public static function error($message, $category = 'application')
     {
-        static::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category);
+        static::getLogger()->log($message, Logger::LEVEL_ERROR, $category);
     }
 
     /**
@@ -383,7 +406,7 @@ class BaseYii
      */
     public static function warning($message, $category = 'application')
     {
-        static::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category);
+        static::getLogger()->log($message, Logger::LEVEL_WARNING, $category);
     }
 
     /**
@@ -395,7 +418,7 @@ class BaseYii
      */
     public static function info($message, $category = 'application')
     {
-        static::$app->getLog()->log($message, Logger::LEVEL_INFO, $category);
+        static::getLogger()->log($message, Logger::LEVEL_INFO, $category);
     }
 
     /**
@@ -417,7 +440,7 @@ class BaseYii
      */
     public static function beginProfile($token, $category = 'application')
     {
-        static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
+        static::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category);
     }
 
     /**
@@ -429,7 +452,7 @@ class BaseYii
      */
     public static function endProfile($token, $category = 'application')
     {
-        static::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category);
+        static::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category);
     }
 
     /**
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index b97863f..f8df6ae 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -62,6 +62,7 @@ Yii Framework 2 Change Log
 - Bug #2760: Fixed GridView `filterUrl` parameters (qiangxue, AlexGx)
 - Bug #2834: When overriding i18n translation sources from config using `app*` or `yii*` default `app` and `yii` sources were not removed (samdark)
 - Bug #2848: Individual queries should be enclosed within parenthesis in a UNION query (qiangxue)
+- Bug #2862: Using `DbCache` while enabling schema caching may cause infinite loops (qiangxue)
 - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
 - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
 - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
@@ -73,6 +74,7 @@ Yii Framework 2 Change Log
 - Bug: Fixed `$model->load($data)` returned `true` if `$data` and `formName` were empty (samdark)
 - Bug: Fixed issue with `ActiveRelationTrait` preventing `ActiveQuery` from clearing events and behaviors on clone (jom)
 - Bug: `Query::queryScalar` wasn't making `SELECT DISTINCT` queries subqueries (jom)
+- Bug: Fixed use `$files` instead of `self::$_files[$key]` to allow inheritance (pgaultier)
 - Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard)
 - Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard)
 - Enh #497: Removed `\yii\log\Target::logUser` and added `\yii\log\Target::prefix` to support customizing message prefix (qiangxue)
@@ -177,6 +179,7 @@ Yii Framework 2 Change Log
 - Enh: LinkPager can now register relational link tags in the html header for prev, next, first and last page (cebe)
 - Enh: Added `yii\web\UrlRuleInterface` and `yii\web\CompositeUrlRule` (qiangxue)
 - Enh: Added `yii\web\Request::getAuthUser()` and `getAuthPassword()` (qiangxue)
+- Enh: Added summaryOptions and emptyTextOptions to BaseListView (johonunu)
 - Chg #47: Changed Markdown library to cebe/markdown and adjusted Markdown helper API (cebe)
 - Chg #735: Added back `ActiveField::hiddenInput()` (qiangxue)
 - Chg #1186: Changed `Sort` to use comma to separate multiple sort fields and use negative sign to indicate descending sort (qiangxue)
@@ -255,6 +258,8 @@ Yii Framework 2 Change Log
 - Chg: `getComponent()` and `setComponent()` in `Application` and `Module` are renamed to `get()` and `set()` respectively. (qiangxue)
 - Chg: The signature of `Yii::createObject()` is changed. Constructor parameters must be passed as the second parameter. (qiangxue)
 - Chg: `Yii::$objectConfig` is removed. You should use `Yii::$container->set()` to configure default settings of classes. (qiangxue)
+- Chg: Removed `yii\grid\Column::getDataCellContent()` and renamed `yii\grid\DataColumn::getDataCellContent()` to `yii\grid\DataColumn::getDataCellValue()` (cebe)
+- Chg: `yii\log\Logger` is split into `yii\log\Logger` and `yii\log\Dispatcher`. (qiangxue)
 - New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul)
 - New #503: Added `yii\di\Container` and `yii\di\ServiceLocator` (qiangxue)
 - New #706: Added `yii\widgets\Pjax` and enhanced `GridView` to work with `Pjax` to support AJAX-update (qiangxue)
diff --git a/framework/base/Application.php b/framework/base/Application.php
index 11a08b2..506e19c 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -23,7 +23,7 @@ use yii\web\HttpException;
  * @property ErrorHandler $errorHandler The error handler application component. This property is read-only.
  * @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\log\Dispatcher $log The log dispatcher 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"
@@ -415,8 +415,8 @@ abstract class Application extends Module
     }
 
     /**
-     * Returns the log component.
-     * @return \yii\log\Logger the log component
+     * Returns the log dispatcher component.
+     * @return \yii\log\Dispatcher the log dispatcher component
      */
     public function getLog()
     {
@@ -512,7 +512,7 @@ abstract class Application extends Module
     public function coreComponents()
     {
         return [
-            'log' => ['class' => 'yii\log\Logger'],
+            'log' => ['class' => 'yii\log\Dispatcher'],
             'errorHandler' => ['class' => 'yii\base\ErrorHandler'],
             'formatter' => ['class' => 'yii\base\Formatter'],
             'i18n' => ['class' => 'yii\i18n\I18N'],
diff --git a/framework/base/DynamicModel.php b/framework/base/DynamicModel.php
index ed2bb22..eb739a2 100644
--- a/framework/base/DynamicModel.php
+++ b/framework/base/DynamicModel.php
@@ -71,6 +71,7 @@ class DynamicModel extends Model
                 $this->_attributes[$name] = $value;
             }
         }
+        parent::__construct($config);
     }
 
     /**
diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php
index 619d547..8de99d1 100644
--- a/framework/base/ErrorHandler.php
+++ b/framework/base/ErrorHandler.php
@@ -89,7 +89,6 @@ class ErrorHandler extends Component
             if (!YII_ENV_TEST) {
                 exit(1);
             }
-
             return;
         }
 
@@ -145,6 +144,9 @@ class ErrorHandler extends Component
             'message' => $exception->getMessage(),
             'code' => $exception->getCode(),
         ];
+        if (YII_DEBUG) {
+            $array['stack-trace'] = explode("\n", $exception->getTraceAsString());
+        }
         if ($exception instanceof HttpException) {
             $array['status'] = $exception->statusCode;
         }
diff --git a/framework/base/View.php b/framework/base/View.php
index c52da26..8693455 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -141,7 +141,6 @@ class View extends Component
     public function render($view, $params = [], $context = null)
     {
         $viewFile = $this->findViewFile($view, $context);
-
         return $this->renderFile($viewFile, $params, $context);
     }
 
diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php
index 8dd5755..bf8c8d8 100644
--- a/framework/caching/MemCache.php
+++ b/framework/caching/MemCache.php
@@ -78,6 +78,7 @@ class MemCache extends Cache
      */
     private $_servers = [];
 
+
     /**
      * Initializes this application component.
      * It creates the memcache instance and adds memcache servers.
diff --git a/framework/console/Controller.php b/framework/console/Controller.php
index aa54a62..cf39ed1 100644
--- a/framework/console/Controller.php
+++ b/framework/console/Controller.php
@@ -264,10 +264,10 @@ class Controller extends \yii\base\Controller
      * Note that the values setting via options are not available
      * until [[beforeAction()]] is being called.
      *
-     * @param string $id action name
+     * @param string $actionId the action id of the current request
      * @return array the names of the options valid for the action
      */
-    public function options($id)
+    public function options($actionId)
     {
         // $id might be used in subclass to provide options specific to action id
         return ['color', 'interactive'];
diff --git a/framework/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php
index f727bbd..f1c72b6 100644
--- a/framework/console/controllers/FixtureController.php
+++ b/framework/console/controllers/FixtureController.php
@@ -33,7 +33,6 @@ use yii\test\FixtureTrait;
  */
 class FixtureController extends Controller
 {
-
     use FixtureTrait;
 
     /**
@@ -57,13 +56,13 @@ class FixtureController extends Controller
         'yii\test\InitDb',
     ];
 
+
     /**
-     * Returns the names of the global options for this command.
-     * @return array the names of the global options for this command.
+     * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id), [
+        return array_merge(parent::options($actionId), [
             'namespace', 'globalFixtures'
         ]);
     }
diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php
index 4ed1435..e649b66 100644
--- a/framework/console/controllers/MigrateController.php
+++ b/framework/console/controllers/MigrateController.php
@@ -92,14 +92,13 @@ class MigrateController extends Controller
     public $db = 'db';
 
     /**
-     * Returns the names of the global options for this command.
-     * @return array the names of the global options for this command.
+     * @inheritdoc
      */
-    public function options($id)
+    public function options($actionId)
     {
-        return array_merge(parent::options($id),
+        return array_merge(parent::options($actionId),
             ['migrationPath', 'migrationTable', 'db'], // global for all actions
-            ($id == 'create') ? ['templateFile'] : [] // action create
+            ($actionId == 'create') ? ['templateFile'] : [] // action create
         );
     }
 
diff --git a/framework/data/ArrayDataProvider.php b/framework/data/ArrayDataProvider.php
index bc5a5bc..a9e1d5a 100644
--- a/framework/data/ArrayDataProvider.php
+++ b/framework/data/ArrayDataProvider.php
@@ -64,6 +64,7 @@ class ArrayDataProvider extends BaseDataProvider
      */
     public $allModels;
 
+
     /**
      * @inheritdoc
      */
diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php
index 5805ed1..8ca51d3 100644
--- a/framework/db/ActiveQuery.php
+++ b/framework/db/ActiveQuery.php
@@ -91,6 +91,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
      */
     public $joinWith;
 
+
     /**
      * Executes query and returns all results as an array.
      * @param Connection $db the DB connection used to create the DB command.
@@ -232,7 +233,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 
     /**
      * Creates a DB command that can be used to execute this query.
-     * @param Connection $db the DB connection used to create the DB command.
+     * @param Connection|null $db the DB connection used to create the DB command.
      * If null, the DB connection returned by [[modelClass]] will be used.
      * @return Command the created DB command instance.
      */
@@ -260,7 +261,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 
     /**
      * Creates a command for lazy loading of a relation.
-     * @param Connection $db the DB connection used to create the DB command.
+     * @param Connection|null $db the DB connection used to create the DB command.
      * @return Command the created DB command instance.
      */
     private function createRelationalCommand($db = null)
@@ -348,6 +349,9 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 
     private function buildJoinWith()
     {
+        $join = $this->join;
+        $this->join = [];
+
         foreach ($this->joinWith as $config) {
             list ($with, $eagerLoading, $joinType) = $config;
             $this->joinWithRelations(new $this->modelClass, $with, $joinType);
@@ -368,6 +372,12 @@ class ActiveQuery extends Query implements ActiveQueryInterface
 
             $this->with($with);
         }
+
+        if (!empty($join)) {
+            // append explicit join to joinWith()
+            // https://github.com/yiisoft/yii2/issues/2880
+            $this->join = empty($this->join) ? $join : array_merge($this->join, $join);
+        }
     }
 
     /**
@@ -524,7 +534,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
         } else {
             $on = $child->on;
         }
-        $this->join($joinType, $childTable, $on);
+        $this->join($joinType, empty($child->from) ? $childTable : $child->from, $on);
 
         if (!empty($child->where)) {
             $this->andWhere($child->where);
diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php
index 2c95ddf..6cc1bbb 100644
--- a/framework/db/ActiveRelationTrait.php
+++ b/framework/db/ActiveRelationTrait.php
@@ -356,12 +356,39 @@ trait ActiveRelationTrait
         return $buckets;
     }
 
+    private function prefixKeyColumns($attributes)
+    {
+        if ($this instanceof ActiveQuery && (!empty($this->join) || !empty($this->joinWith))) {
+            if (empty($this->from)) {
+                /** @var ActiveRecord $modelClass */
+                $modelClass = $this->modelClass;
+                $alias = $modelClass::tableName();
+            } else {
+                foreach ($this->from as $alias => $table) {
+                    if (!is_string($alias)) {
+                        $alias = $table;
+                    }
+                    break;
+                }
+            }
+            if (isset($alias)) {
+                foreach ($attributes as $i => $attribute) {
+                	$attributes[$i] = "$alias.$attribute";
+                }
+            }
+        }
+        return $attributes;
+    }
+
     /**
      * @param array $models
      */
     private function filterByModels($models)
     {
         $attributes = array_keys($this->link);
+
+        $attributes = $this->prefixKeyColumns($attributes);
+
         $values = [];
         if (count($attributes) === 1) {
             // single key
diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php
index 3ac93e3..a62308c 100644
--- a/framework/db/BaseActiveRecord.php
+++ b/framework/db/BaseActiveRecord.php
@@ -81,7 +81,8 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
      */
     private $_attributes = [];
     /**
-     * @var array old attribute values indexed by attribute names.
+     * @var array|null old attribute values indexed by attribute names.
+     * This is `null` if the record [[isNewRecord|is new]].
      */
     private $_oldAttributes;
     /**
@@ -475,7 +476,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
     /**
      * Sets the old attribute values.
      * All existing old attribute values will be discarded.
-     * @param array $values old attribute values to be set.
+     * @param array|null $values old attribute values to be set.
      */
     public function setOldAttributes($values)
     {
@@ -1304,8 +1305,8 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
 
     /**
      * @param array $link
-     * @param BaseActiveRecord $foreignModel
-     * @param BaseActiveRecord $primaryModel
+     * @param ActiveRecordInterface $foreignModel
+     * @param ActiveRecordInterface $primaryModel
      * @throws InvalidCallException
      */
     private function bindModels($link, $foreignModel, $primaryModel)
diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php
index 1c94b34..b9b5e4c 100644
--- a/framework/db/QueryBuilder.php
+++ b/framework/db/QueryBuilder.php
@@ -65,18 +65,7 @@ class QueryBuilder extends \yii\base\Object
     public function build($query, $params = [])
     {
         $params = empty($params) ? $query->params : array_merge($params, $query->params);
-
-        $select = $query->select;
-        $from = $query->from;
-        if ($from === null && $query instanceof ActiveQuery) {
-            /** @var ActiveRecord $modelClass */
-            $modelClass = $query->modelClass;
-            $tableName = $modelClass::tableName();
-            $from = [$tableName];
-            if ($select === null && !empty($query->join)) {
-                $select = ["$tableName.*"];
-            }
-        }
+        list ($select, $from) = $this->adjustSelectFrom($query);
 
         $clauses = [
             $this->buildSelect($select, $params, $query->distinct, $query->selectOption),
@@ -100,6 +89,44 @@ class QueryBuilder extends \yii\base\Object
     }
 
     /**
+     * Adjusts the select and from parts of the query when it is an ActiveQuery.
+     * When ActiveQuery does not specify "from", or if it is a join query without explicit "select",
+     * certain adjustments need to be made. This method is put here so that QueryBuilder can
+     * support sub-queries.
+     * @param Query $query
+     * @return array the select and from parts.
+     */
+    protected function adjustSelectFrom($query)
+    {
+        $select = $query->select;
+        $from = $query->from;
+        if ($query instanceof ActiveQuery && (empty($select) || empty($from))) {
+            /** @var ActiveRecord $modelClass */
+            $modelClass = $query->modelClass;
+            $tableName = $modelClass::tableName();
+            if (empty($from)) {
+                $from = [$tableName];
+            }
+            if (empty($select) && !empty($query->join)) {
+                foreach ((array)$from as $alias => $table) {
+                	if (is_string($alias)) {
+                        $select = ["$alias.*"];
+                    } elseif (is_string($table)) {
+                        if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $table, $matches)) {
+                            $alias = $matches[2];
+                        } else {
+                            $alias = $tableName;
+                        }
+                        $select = ["$alias.*"];
+                    }
+                    break;
+                }
+            }
+        }
+        return [$select, $from];
+    }
+
+    /**
      * Creates an INSERT SQL statement.
      * For example,
      *
@@ -642,23 +669,7 @@ class QueryBuilder extends \yii\base\Object
             return '';
         }
 
-        foreach ($tables as $i => $table) {
-            if ($table instanceof Query) {
-                list($sql, $params) = $this->build($table, $params);
-                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
-            } elseif (is_string($i)) {
-                if (strpos($table, '(') === false) {
-                    $table = $this->db->quoteTableName($table);
-                }
-                $tables[$i] = "$table " . $this->db->quoteTableName($i);
-            } elseif (strpos($table, '(') === false) {
-                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
-                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
-                } else {
-                    $tables[$i] = $this->db->quoteTableName($table);
-                }
-            }
-        }
+        $tables = $this->quoteTableNames($tables, $params);
 
         return 'FROM ' . implode(', ', $tables);
     }
@@ -681,21 +692,8 @@ class QueryBuilder extends \yii\base\Object
             }
             // 0:join type, 1:join table, 2:on-condition (optional)
             list ($joinType, $table) = $join;
-            if (is_array($table)) {
-                $query = reset($table);
-                if (!$query instanceof Query) {
-                    throw new Exception('The sub-query for join must be an instance of yii\db\Query.');
-                }
-                $alias = $this->db->quoteTableName(key($table));
-                list ($sql, $params) = $this->build($query, $params);
-                $table = "($sql) $alias";
-            } elseif (strpos($table, '(') === false) {
-                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
-                    $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
-                } else {
-                    $table = $this->db->quoteTableName($table);
-                }
-            }
+            $tables = $this->quoteTableNames((array)$table, $params);
+            $table = reset($tables);
             $joins[$i] = "$joinType $table";
             if (isset($join[2])) {
                 $condition = $this->buildCondition($join[2], $params);
@@ -708,6 +706,28 @@ class QueryBuilder extends \yii\base\Object
         return implode($this->separator, $joins);
     }
 
+    private function quoteTableNames($tables, &$params)
+    {
+        foreach ($tables as $i => $table) {
+            if ($table instanceof Query) {
+                list($sql, $params) = $this->build($table, $params);
+                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
+            } elseif (is_string($i)) {
+                if (strpos($table, '(') === false) {
+                    $table = $this->db->quoteTableName($table);
+                }
+                $tables[$i] = "$table " . $this->db->quoteTableName($i);
+            } elseif (strpos($table, '(') === false) {
+                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
+                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
+                } else {
+                    $tables[$i] = $this->db->quoteTableName($table);
+                }
+            }
+        }
+        return $tables;
+    }
+
     /**
      * @param string|array $condition
      * @param array $params the binding parameters to be populated
diff --git a/framework/db/Schema.php b/framework/db/Schema.php
index 7cdb46c..fdfcf2d 100644
--- a/framework/db/Schema.php
+++ b/framework/db/Schema.php
@@ -87,7 +87,7 @@ abstract class Schema extends Object
      */
     public function getTableSchema($name, $refresh = false)
     {
-        if (isset($this->_tables[$name]) && !$refresh) {
+        if (array_key_exists($name, $this->_tables) && !$refresh) {
             return $this->_tables[$name];
         }
 
@@ -100,15 +100,17 @@ abstract class Schema extends Object
             if ($cache instanceof Cache) {
                 $key = $this->getCacheKey($name);
                 if ($refresh || ($table = $cache->get($key)) === false) {
-                    $table = $this->loadTableSchema($realName);
+                    $this->_tables[$name] = $table = $this->loadTableSchema($realName);
                     if ($table !== null) {
                         $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency([
                             'group' => $this->getCacheGroup(),
                         ]));
                     }
+                } else {
+                    $this->_tables[$name] = $table;
                 }
 
-                return $this->_tables[$name] = $table;
+                return $this->_tables[$name];
             }
         }
 
diff --git a/framework/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php
index f9b14fd..a27d919 100644
--- a/framework/db/oci/QueryBuilder.php
+++ b/framework/db/oci/QueryBuilder.php
@@ -8,6 +8,8 @@
 namespace yii\db\oci;
 
 use yii\base\InvalidParamException;
+use yii\db\ActiveQuery;
+use yii\db\ActiveRecord;
 
 /**
  * QueryBuilder is the query builder for Oracle databases.
@@ -26,10 +28,11 @@ class QueryBuilder extends \yii\db\QueryBuilder
     public function build($query, $params = [])
     {
         $params = empty($params) ? $query->params : array_merge($params, $query->params);
+        list ($select, $from) = $this->adjustSelectFrom($query);
 
         $clauses = [
-            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
-            $this->buildFrom($query->from, $params),
+            $this->buildSelect($select, $params, $query->distinct, $query->selectOption),
+            $this->buildFrom($from, $params),
             $this->buildJoin($query->join, $params),
             $this->buildWhere($query->where, $params),
             $this->buildGroupBy($query->groupBy),
diff --git a/framework/grid/Column.php b/framework/grid/Column.php
index 945d6c4..44395d6 100644
--- a/framework/grid/Column.php
+++ b/framework/grid/Column.php
@@ -99,7 +99,6 @@ class Column extends Object
         } else {
             $options = $this->contentOptions;
         }
-
         return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options);
     }
 
@@ -134,35 +133,22 @@ class Column extends Object
     }
 
     /**
-     * Returns the raw data cell content.
-     * This method is called by [[renderDataCellContent()]] when rendering the content of a data cell.
+     * 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 [[GridView::dataProvider]].
      * @return string the rendering result
      */
-    protected function getDataCellContent($model, $key, $index)
+    protected function renderDataCellContent($model, $key, $index)
     {
         if ($this->content !== null) {
             return call_user_func($this->content, $model, $key, $index, $this);
         } else {
-            return null;
+            return $this->grid->emptyCell;
         }
     }
 
     /**
-     * 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 [[GridView::dataProvider]].
-     * @return string the rendering result
-     */
-    protected function renderDataCellContent($model, $key, $index)
-    {
-        return $this->content !== null ? $this->getDataCellContent($model, $key, $index) : $this->grid->emptyCell;
-    }
-
-    /**
      * Renders the filter cell content.
      * The default implementation simply renders a space.
      * This method may be overridden to customize the rendering of the filter cell (if any).
diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php
index 2facd2a..30005dd 100644
--- a/framework/grid/DataColumn.php
+++ b/framework/grid/DataColumn.php
@@ -17,7 +17,17 @@ use yii\helpers\Inflector;
 /**
  * DataColumn is the default column type for the [[GridView]] widget.
  *
- * It is used to show data columns and allows sorting them.
+ * It is used to show data columns and allows [[enableSorting|sorting]] and [[filter|filtering]] them.
+ *
+ * A simple data column definition refers to an attribute in the data model of the
+ * GridView's data provider. The name of the attribute is specified by [[attribute]].
+ *
+ * By setting [[value]] and [[label]], the header and cell content can be customized.
+ *
+ * A data column differentiates between the [[getDataCellValue|data cell value]] and the
+ * [[renderDataCellContent|data cell content]]. The cell value is an un-formatted value that
+ * may be used for calculation, while the actual cell content is a [[format|formatted]] version of that
+ * value which may contain HTML markup.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
@@ -40,10 +50,13 @@ class DataColumn extends Column
      */
     public $label;
     /**
-     * @var string|\Closure the attribute name to be displayed in this column or an anonymous function that returns
-     * the value to be displayed for every data model.
+     * @var string|\Closure an anonymous function that returns the value to be displayed for every data model.
      * The signature of this function is `function ($model, $index, $widget)`.
      * If this is not set, `$model[$attribute]` will be used to obtain the value.
+     *
+     * You may also set this property to a string representing the attribute name to be displayed in this column.
+     * This can be used when the attribute to be displayed is different from the [[attribute]] that is used for
+     * sorting and filtering.
      */
     public $value;
     /**
@@ -133,7 +146,6 @@ class DataColumn extends Column
         {
             if (is_array($this->filter)) {
                 $options = array_merge(['prompt' => ''], $this->filterInputOptions);
-
                 return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options);
             } else {
                 return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions);
@@ -146,21 +158,18 @@ class DataColumn extends Column
     /**
      * @inheritdoc
      */
-    protected function getDataCellContent($model, $key, $index)
+    protected function getDataCellValue($model, $key, $index)
     {
         if ($this->value !== null) {
             if (is_string($this->value)) {
-                $value = ArrayHelper::getValue($model, $this->value);
+                return ArrayHelper::getValue($model, $this->value);
             } else {
-                $value = call_user_func($this->value, $model, $index, $this);
+                return call_user_func($this->value, $model, $index, $this);
             }
-        } elseif ($this->content === null && $this->attribute !== null) {
-            $value = ArrayHelper::getValue($model, $this->attribute);
-        } else {
-            return parent::getDataCellContent($model, $key, $index);
+        } elseif ($this->attribute !== null) {
+            return ArrayHelper::getValue($model, $this->attribute);
         }
-
-        return $value;
+        return null;
     }
 
     /**
@@ -168,6 +177,10 @@ class DataColumn extends Column
      */
     protected function renderDataCellContent($model, $key, $index)
     {
-        return $this->grid->formatter->format($this->getDataCellContent($model, $key, $index), $this->format);
+        if ($this->content === null) {
+            return $this->grid->formatter->format($this->getDataCellValue($model, $key, $index), $this->format);
+        } else {
+            return parent::renderDataCellContent($model, $key, $index);
+        }
     }
 }
diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php
index 8ead84f..4172688 100644
--- a/framework/helpers/BaseArrayHelper.php
+++ b/framework/helpers/BaseArrayHelper.php
@@ -62,7 +62,7 @@ class BaseArrayHelper
             if ($recursive) {
                 foreach ($object as $key => $value) {
                     if (is_array($value) || is_object($value)) {
-                        $object[$key] = static::toArray($value, true);
+                        $object[$key] = static::toArray($value, $properties, true);
                     }
                 }
             }
diff --git a/framework/helpers/BaseFileHelper.php b/framework/helpers/BaseFileHelper.php
index 74b04ab..29f05ef 100644
--- a/framework/helpers/BaseFileHelper.php
+++ b/framework/helpers/BaseFileHelper.php
@@ -285,14 +285,14 @@ class BaseFileHelper
             if (isset($options['except'])) {
                 foreach ($options['except'] as $key => $value) {
                     if (is_string($value)) {
-                        $options['except'][$key] = static::parseExcludePattern($value);
+                        $options['except'][$key] = self::parseExcludePattern($value);
                     }
                 }
             }
             if (isset($options['only'])) {
                 foreach ($options['only'] as $key => $value) {
                     if (is_string($value)) {
-                        $options['only'][$key] = static::parseExcludePattern($value);
+                        $options['only'][$key] = self::parseExcludePattern($value);
                     }
                 }
             }
diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php
index ca12d27..07f4ed5 100644
--- a/framework/helpers/BaseHtml.php
+++ b/framework/helpers/BaseHtml.php
@@ -342,7 +342,7 @@ class BaseHtml
 
     /**
      * Generates an image tag.
-     * @param string $src the image URL. This parameter will be processed by [[yii\helpers\Url::to()]].
+     * @param array|string $src the image URL. This parameter will be processed by [[yii\helpers\Url::to()]].
      * @param array $options the tag options in terms of name-value pairs. These will be rendered as
      * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
      * If a value is null, the corresponding attribute will not be rendered.
diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php
index 59bb3ec..6d8ee39 100644
--- a/framework/helpers/BaseMarkdown.php
+++ b/framework/helpers/BaseMarkdown.php
@@ -82,7 +82,7 @@ class BaseMarkdown
      * @return \cebe\markdown\Parser
      * @throws \yii\base\InvalidParamException when an undefined flavor is given.
      */
-    private static function getParser($flavor)
+    protected static function getParser($flavor)
     {
         /** @var \cebe\markdown\Markdown $parser */
         if (!isset(static::$flavors[$flavor])) {
diff --git a/framework/helpers/BaseUrl.php b/framework/helpers/BaseUrl.php
index d43882f..eb97526 100644
--- a/framework/helpers/BaseUrl.php
+++ b/framework/helpers/BaseUrl.php
@@ -108,7 +108,7 @@ class BaseUrl
      * @return string normalized route suitable for UrlManager
      * @throws InvalidParamException a relative route is given while there is no active controller
      */
-    private static function normalizeRoute($route)
+    protected static function normalizeRoute($route)
     {
         $route = (string) $route;
         if (strncmp($route, '/', 1) === 0) {
diff --git a/framework/i18n/MessageFormatter.php b/framework/i18n/MessageFormatter.php
index 0708f69..893322f 100644
--- a/framework/i18n/MessageFormatter.php
+++ b/framework/i18n/MessageFormatter.php
@@ -220,7 +220,9 @@ class MessageFormatter extends Component
                 if (!isset($token[2])) {
                     return false;
                 }
-                $subtokens = self::tokenizePattern($token[2]);
+                if (($subtokens = self::tokenizePattern($token[2])) === false) {
+                    return false;
+                }
                 $c = count($subtokens);
                 for ($k = 0; $k + 1 < $c; $k++) {
                     if (is_array($subtokens[$k]) || !is_array($subtokens[++$k])) {
diff --git a/framework/log/Dispatcher.php b/framework/log/Dispatcher.php
new file mode 100644
index 0000000..7a090b9
--- /dev/null
+++ b/framework/log/Dispatcher.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\log;
+
+use Yii;
+use yii\base\Component;
+
+/**
+ * Dispatcher manages a set of [[Target|log targets]].
+ *
+ * Dispatcher implements [[dispatch()]] that forwards the log messages from [[Logger]] to
+ * the registered log [[targets]].
+ *
+ * Dispatcher is registered as a core application component and can be accessed using `Yii::$app->log`.
+ *
+ * You may configure the targets in application configuration, like the following:
+ *
+ * ~~~
+ * [
+ *     'components' => [
+ *         'log' => [
+ *             'targets' => [
+ *                 'file' => [
+ *                     'class' => 'yii\log\FileTarget',
+ *                     'levels' => ['trace', 'info'],
+ *                     'categories' => ['yii\*'],
+ *                 ],
+ *                 'email' => [
+ *                     'class' => 'yii\log\EmailTarget',
+ *                     'levels' => ['error', 'warning'],
+ *                     'message' => [
+ *                         'to' => 'admin@example.com',
+ *                     ],
+ *                 ],
+ *             ],
+ *         ],
+ *     ],
+ * ]
+ * ~~~
+ *
+ * Each log target can have a name and can be referenced via the [[targets]] property
+ * as follows:
+ *
+ * ~~~
+ * Yii::$app->log->targets['file']->enabled = false;
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Dispatcher extends Component
+{
+    /**
+     * @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance
+     * or the configuration for creating the log target instance.
+     */
+    public $targets = [];
+    /**
+     * @var Logger the logger. If not set, [[\Yii::getLogger()]] will be used.
+     */
+    public $logger;
+
+    /**
+     * Initializes the logger by registering [[flush()]] as a shutdown function.
+     */
+    public function init()
+    {
+        parent::init();
+
+        foreach ($this->targets as $name => $target) {
+            if (!$target instanceof Target) {
+                $this->targets[$name] = Yii::createObject($target);
+            }
+        }
+
+        if ($this->logger === null) {
+            $this->logger = Yii::getLogger();
+        }
+        $this->logger->dispatcher = $this;
+    }
+
+    /**
+     * @return integer how many application call stacks should be logged together with each message.
+     * This method returns the value of [[Logger::traceLevel]]. Defaults to 0.
+     */
+    public function getTraceLevel()
+    {
+        return $this->logger->traceLevel;
+    }
+
+    /**
+     * @param integer $value how many application call stacks should be logged together with each message.
+     * This method will set the value of [[Logger::traceLevel]]. If the value is greater than 0,
+     * at most that number of call stacks will be logged. Note that only application call stacks are counted.
+     * Defaults to 0.
+     */
+    public function setTraceLevel($value)
+    {
+        $this->logger->traceLevel = $value;
+    }
+
+    /**
+     * @return integer how many messages should be logged before they are sent to targets.
+     * This method returns the value of [[Logger::flushInterval]].
+     */
+    public function getFlushInterval()
+    {
+        return $this->logger->flushInterval;
+    }
+
+    /**
+     * @param integer $value how many messages should be logged before they are sent to targets.
+     * This method will set the value of [[Logger::flushInterval]].
+     * Defaults to 1000, meaning the [[Logger::flush()]] method will be invoked once every 1000 messages logged.
+     * Set this property to be 0 if you don't want to flush messages until the application terminates.
+     * This property mainly affects how much memory will be taken by the logged messages.
+     * A smaller value means less memory, but will increase the execution time due to the overhead of [[Logger::flush()]].
+     */
+    public function setFlushInterval($value)
+    {
+        $this->logger->flushInterval = $value;
+    }
+
+    /**
+     * Dispatches the logged messages to [[targets]].
+     * @param array $messages the logged messages
+     * @param boolean $final whether this method is called at the end of the current application
+     */
+    public function dispatch($messages, $final)
+    {
+        foreach ($this->targets as $target) {
+            if ($target->enabled) {
+                $target->collect($messages, $final);
+            }
+        }
+    }
+}
diff --git a/framework/log/Logger.php b/framework/log/Logger.php
index b067e39..029f474 100644
--- a/framework/log/Logger.php
+++ b/framework/log/Logger.php
@@ -11,11 +11,11 @@ use Yii;
 use yii\base\Component;
 
 /**
- * Logger records logged messages in memory and sends them to different targets as needed.
+ * Logger records logged messages in memory and sends them to different targets if [[dispatcher]] is set.
  *
- * Logger is registered as a core application component and can be accessed using `Yii::$app->log`.
- * You can call the method [[log()]] to record a single log message. For convenience, a set of shortcut
- * methods are provided for logging messages of various severity levels via the [[Yii]] class:
+ * Logger can be accessed via `Yii::getLogger()`. You can call the method [[log()]] to record a single log message.
+ * For convenience, a set of shortcut methods are provided for logging messages of various severity levels
+ * via the [[Yii]] class:
  *
  * - [[Yii::trace()]]
  * - [[Yii::error()]]
@@ -24,43 +24,8 @@ use yii\base\Component;
  * - [[Yii::beginProfile()]]
  * - [[Yii::endProfile()]]
  *
- * When enough messages are accumulated in the logger, or when the current request finishes,
- * the logged messages will be sent to different [[targets]], such as log files, emails.
- *
- * You may configure the targets in application configuration, like the following:
- *
- * ~~~
- * [
- *     'components' => [
- *         'log' => [
- *             'targets' => [
- *                 'file' => [
- *                     'class' => 'yii\log\FileTarget',
- *                     'levels' => ['trace', 'info'],
- *                     'categories' => ['yii\*'],
- *                 ],
- *                 'email' => [
- *                     'class' => 'yii\log\EmailTarget',
- *                     'levels' => ['error', 'warning'],
- *                     'message' => [
- *                         'to' => 'admin@example.com',
- *                     ],
- *                 ],
- *             ],
- *         ],
- *     ],
- * ]
- * ~~~
- *
- * Each log target can have a name and can be referenced via the [[targets]] property
- * as follows:
- *
- * ~~~
- * Yii::$app->log->targets['file']->enabled = false;
- * ~~~
- *
  * When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
- * to send logged messages to different log targets, such as file, email, Web.
+ * to send logged messages to different log targets, such as file, email, Web, with the help of [[dispatcher]].
  *
  * @property array $dbProfiling The first element indicates the number of SQL statements executed, and the
  * second element the total time spent in SQL execution. This property is read-only.
@@ -125,16 +90,6 @@ class Logger extends Component
      */
     public $messages = [];
     /**
-     * @var array debug data. This property stores various types of debug data reported at
-     * different instrument places.
-     */
-    public $data = [];
-    /**
-     * @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance
-     * or the configuration for creating the log target instance.
-     */
-    public $targets = [];
-    /**
      * @var integer how many messages should be logged before they are flushed from memory and sent to targets.
      * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged.
      * Set this property to be 0 if you don't want to flush messages until the application terminates.
@@ -146,10 +101,13 @@ class Logger extends Component
      * @var integer how much call stack information (file name and line number) should be logged for each message.
      * If it is greater than 0, at most that number of call stacks will be logged. Note that only application
      * call stacks are counted.
-     *
-     * If not set, it will default to 3 when `YII_ENV` is set as "dev", and 0 otherwise.
      */
-    public $traceLevel;
+    public $traceLevel = 0;
+    /**
+     * @var Dispatcher the message dispatcher
+     */
+    public $dispatcher;
+
 
     /**
      * Initializes the logger by registering [[flush()]] as a shutdown function.
@@ -157,14 +115,6 @@ class Logger extends Component
     public function init()
     {
         parent::init();
-        if ($this->traceLevel === null) {
-            $this->traceLevel = YII_ENV_DEV ? 3 : 0;
-        }
-        foreach ($this->targets as $name => $target) {
-            if (!$target instanceof Target) {
-                $this->targets[$name] = Yii::createObject($target);
-            }
-        }
         register_shutdown_function([$this, 'flush'], true);
     }
 
@@ -208,11 +158,8 @@ class Logger extends Component
      */
     public function flush($final = false)
     {
-        /** @var Target $target */
-        foreach ($this->targets as $target) {
-            if ($target->enabled) {
-                $target->collect($this->messages, $final);
-            }
+        if ($this->dispatcher instanceof Dispatcher) {
+            $this->dispatcher->dispatch($this->messages, $final);
         }
         $this->messages = [];
     }
diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php
index 92678e2..b3a1b7a 100644
--- a/framework/rbac/PhpManager.php
+++ b/framework/rbac/PhpManager.php
@@ -44,6 +44,7 @@ class PhpManager extends Manager
     private $_children = []; // itemName, childName => child
     private $_assignments = []; // userId, itemName => assignment
 
+
     /**
      * Initializes the application component.
      * This method overrides parent implementation by loading the authorization data
@@ -158,7 +159,7 @@ class PhpManager extends Manager
 
     /**
      * Returns the children of the specified item.
-     * @param mixed $names the parent item name. This can be either a string or an array.
+     * @param string|array $names the parent item name. This can be either a string or an array.
      * The latter represents a list of item names.
      * @return Item[] all child items of the parent
      */
diff --git a/framework/requirements/views/console/index.php b/framework/requirements/views/console/index.php
index 1f92c68..dbd1c12 100644
--- a/framework/requirements/views/console/index.php
+++ b/framework/requirements/views/console/index.php
@@ -1,7 +1,9 @@
 <?php
-/* @var YiiRequirementChecker $this */
-/* @var array $summary */
-/* @var array[] $requirements */
+/**
+ * @var YiiRequirementChecker $this
+ * @var array $summary
+ * @var array[] $requirements
+ */
 
 echo "\nYii Application Requirement Checker\n\n";
 
diff --git a/framework/requirements/views/web/index.php b/framework/requirements/views/web/index.php
index 600e6d4..721a279 100644
--- a/framework/requirements/views/web/index.php
+++ b/framework/requirements/views/web/index.php
@@ -1,9 +1,10 @@
 <?php
-/* @var YiiRequirementChecker $this */
-/* @var array $summary */
-/* @var array[] $requirements */
-?>
-<!DOCTYPE html>
+/**
+ * @var YiiRequirementChecker $this
+ * @var array $summary
+ * @var array[] $requirements
+ */
+?><!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="utf-8"/>
diff --git a/framework/rest/Controller.php b/framework/rest/Controller.php
index d17a14c..3239aa8 100644
--- a/framework/rest/Controller.php
+++ b/framework/rest/Controller.php
@@ -105,10 +105,9 @@ class Controller extends \yii\web\Controller
      */
     public function beforeAction($action)
     {
+        $this->authenticate($action);
         if (parent::beforeAction($action)) {
-            $this->authenticate($action);
             $this->checkRateLimit($action);
-
             return true;
         } else {
             return false;
@@ -121,7 +120,6 @@ class Controller extends \yii\web\Controller
     public function afterAction($action, $result)
     {
         $result = parent::afterAction($action, $result);
-
         return $this->serializeData($result);
     }
 
diff --git a/framework/rest/RateLimiter.php b/framework/rest/RateLimiter.php
index d668e6d..8226f59 100644
--- a/framework/rest/RateLimiter.php
+++ b/framework/rest/RateLimiter.php
@@ -8,7 +8,6 @@
 namespace yii\rest;
 
 use yii\base\Component;
-use yii\base\Action;
 use yii\web\Request;
 use yii\web\Response;
 use yii\web\TooManyRequestsHttpException;
@@ -37,7 +36,7 @@ class RateLimiter extends Component
      * @param RateLimitInterface $user the current user
      * @param Request $request
      * @param Response $response
-     * @param Action $action the action to be executed
+     * @param \yii\base\Action $action the action to be executed
      * @throws TooManyRequestsHttpException if rate limit exceeds
      */
     public function check($user, $request, $response, $action)
diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php
index 20756eb..0990910 100644
--- a/framework/validators/FileValidator.php
+++ b/framework/validators/FileValidator.php
@@ -232,9 +232,8 @@ class FileValidator extends Validator
      */
     public function isEmpty($value, $trim = false)
     {
-            $value = is_array($value) && !empty($value) ? $value[0] : $value;
-
-            return !$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE;
+        $value = is_array($value) && !empty($value) ? $value[0] : $value;
+        return !($value instanceof UploadedFile) || $value->error == UPLOAD_ERR_NO_FILE;
     }
 
     /**
diff --git a/framework/web/AccessControl.php b/framework/web/AccessControl.php
index 077f9bd..da3d63b 100644
--- a/framework/web/AccessControl.php
+++ b/framework/web/AccessControl.php
@@ -64,6 +64,7 @@ class AccessControl extends ActionFilter
      * ~~~
      *
      * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+     * `$rule` will be `null` if access is denied because none of the rules matched.
      */
     public $denyCallback;
     /**
@@ -79,6 +80,7 @@ class AccessControl extends ActionFilter
      */
     public $rules = [];
 
+
     /**
      * Initializes the [[rules]] array by instantiating rule objects from configurations.
      */
@@ -114,16 +116,14 @@ class AccessControl extends ActionFilter
                 } else {
                     $this->denyAccess($user);
                 }
-
                 return false;
             }
         }
         if (isset($this->denyCallback)) {
-            call_user_func($this->denyCallback, $rule, $action);
+            call_user_func($this->denyCallback, null, $action);
         } else {
             $this->denyAccess($user);
         }
-
         return false;
     }
 
diff --git a/framework/web/Request.php b/framework/web/Request.php
index 87a425b..75c7407 100644
--- a/framework/web/Request.php
+++ b/framework/web/Request.php
@@ -124,8 +124,8 @@ class Request extends \yii\base\Request
      */
     public $enableCookieValidation = true;
     /**
-     * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
-     * request tunneled through POST. Default to '_method'.
+     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
+     * request tunneled through POST. Defaults to '_method'.
      * @see getMethod()
      * @see getBodyParams()
      */
diff --git a/framework/web/Response.php b/framework/web/Response.php
index c360582..9b4232d 100644
--- a/framework/web/Response.php
+++ b/framework/web/Response.php
@@ -738,7 +738,6 @@ class Response extends \yii\base\Response
         if ($this->_cookies === null) {
             $this->_cookies = new CookieCollection;
         }
-
         return $this->_cookies;
     }
 
diff --git a/framework/web/UploadedFile.php b/framework/web/UploadedFile.php
index c7648c1..66fb37c 100644
--- a/framework/web/UploadedFile.php
+++ b/framework/web/UploadedFile.php
@@ -56,6 +56,7 @@ class UploadedFile extends Object
      */
     public $error;
 
+
     /**
      * String output.
      * This is PHP magic method that returns string representation of an object.
@@ -80,7 +81,6 @@ class UploadedFile extends Object
     public static function getInstance($model, $attribute)
     {
         $name = Html::getInputName($model, $attribute);
-
         return static::getInstanceByName($name);
     }
 
@@ -95,7 +95,6 @@ class UploadedFile extends Object
     public static function getInstances($model, $attribute)
     {
         $name = Html::getInputName($model, $attribute);
-
         return static::getInstancesByName($name);
     }
 
@@ -108,8 +107,7 @@ class UploadedFile extends Object
      */
     public static function getInstanceByName($name)
     {
-        $files = static::loadFiles();
-
+        $files = self::loadFiles();
         return isset($files[$name]) ? $files[$name] : null;
     }
 
@@ -124,17 +122,16 @@ class UploadedFile extends Object
      */
     public static function getInstancesByName($name)
     {
-        $files = static::loadFiles();
+        $files = self::loadFiles();
         if (isset($files[$name])) {
             return [$files[$name]];
         }
         $results = [];
         foreach ($files as $key => $file) {
             if (strpos($key, "{$name}[") === 0) {
-                $results[] = self::$_files[$key];
+                $results[] = $file;
             }
         }
-
         return $results;
     }
 
@@ -166,7 +163,6 @@ class UploadedFile extends Object
                 return copy($this->tempName, $file);
             }
         }
-
         return false;
     }
 
@@ -209,7 +205,6 @@ class UploadedFile extends Object
                 }
             }
         }
-
         return self::$_files;
     }
 
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index cb73e70..fdb0ddf 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -101,6 +101,7 @@ class UrlRule extends Object implements UrlRuleInterface
      */
     private $_routeParams = [];
 
+
     /**
      * Initializes this rule.
      */
diff --git a/framework/web/User.php b/framework/web/User.php
index 937f561..2d41474 100644
--- a/framework/web/User.php
+++ b/framework/web/User.php
@@ -141,8 +141,8 @@ class User extends Component
      * @param boolean $checkSession whether to check the session if the identity has never been determined before.
      * If the identity is already determined (e.g., by calling [[setIdentity()]] or [[login()]]),
      * then this parameter has no effect.
-     * @return IdentityInterface the identity object associated with the currently logged-in user.
-     * Null is returned if the user is not logged in (not authenticated).
+     * @return IdentityInterface|null the identity object associated with the currently logged-in user.
+     * `null` is returned if the user is not logged in (not authenticated).
      * @see login()
      * @see logout()
      */
@@ -170,7 +170,7 @@ class User extends Component
      * [[switchIdentity()]]. Those methods will try to use session and cookie to maintain the user authentication
      * status.
      *
-     * @param IdentityInterface $identity the identity object associated with the currently logged user.
+     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
      */
     public function setIdentity($identity)
     {
diff --git a/framework/widgets/BaseListView.php b/framework/widgets/BaseListView.php
index e31f003..e26519a 100644
--- a/framework/widgets/BaseListView.php
+++ b/framework/widgets/BaseListView.php
@@ -54,6 +54,12 @@ abstract class BaseListView extends Widget
      */
     public $summary;
     /**
+     * @var array the HTML attributes for the summary of the list view.
+     * The "tag" element specifies the tag name of the summary element and defaults to "div".
+     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+     */
+    public $summaryOptions = ['class' => 'summary'];
+    /**
      * @var boolean whether to show the list view if [[dataProvider]] returns no data.
      */
     public $showOnEmpty = false;
@@ -62,6 +68,12 @@ abstract class BaseListView extends Widget
      */
     public $emptyText;
     /**
+     * @var array the HTML attributes for the emptyText of the list view.
+     * The "tag" element specifies the tag name of the emptyText element and defaults to "div".
+     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
+     */
+    public $emptyTextOptions = ['class' => 'empty'];
+    /**
      * @var string the layout that determines how different sections of the list view should be organized.
      * The following tokens will be replaced with the corresponding section contents:
      *
@@ -139,7 +151,8 @@ abstract class BaseListView extends Widget
      */
     public function renderEmpty()
     {
-        return '<div class="empty">' . ($this->emptyText === null ? Yii::t('yii', 'No results found.') : $this->emptyText) . '</div>';
+        $tag = ArrayHelper::remove($this->emptyTextOptions, 'tag', 'div');
+        return Html::tag($tag, ($this->emptyText === null ? Yii::t('yii', 'No results found.') : $this->emptyText), $this->emptyTextOptions);
     }
 
     /**
@@ -151,6 +164,7 @@ abstract class BaseListView extends Widget
         if ($count <= 0) {
             return '';
         }
+        $tag = ArrayHelper::remove($this->summaryOptions, 'tag', 'div');
         if (($pagination = $this->dataProvider->getPagination()) !== false) {
             $totalCount = $this->dataProvider->getTotalCount();
             $begin = $pagination->getPage() * $pagination->pageSize + 1;
@@ -161,29 +175,27 @@ abstract class BaseListView extends Widget
             $page = $pagination->getPage() + 1;
             $pageCount = $pagination->pageCount;
             if (($summaryContent = $this->summary) === null) {
-                return '<div class="summary">'
-                    . Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', [
+                return Html::tag($tag, Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', [
                         'begin' => $begin,
                         'end' => $end,
                         'count' => $count,
                         'totalCount' => $totalCount,
                         'page' => $page,
                         'pageCount' => $pageCount,
-                    ])
-                    . '</div>';
+                    ]), $this->summaryOptions);
             }
         } else {
             $begin = $page = $pageCount = 1;
             $end = $totalCount = $count;
             if (($summaryContent = $this->summary) === null) {
-                return '<div class="summary">' . Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', [
+                return Html::tag($tag, Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', [
                     'begin' => $begin,
                     'end' => $end,
                     'count' => $count,
                     'totalCount' => $totalCount,
                     'page' => $page,
                     'pageCount' => $pageCount,
-                ]) . '</div>';
+                ]), $this->summaryOptions);
             }
         }
 
diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php
index 7be793a..3777464 100644
--- a/tests/unit/framework/db/ActiveRecordTest.php
+++ b/tests/unit/framework/db/ActiveRecordTest.php
@@ -402,6 +402,19 @@ class ActiveRecordTest extends DatabaseTestCase
         $this->assertEquals(3, $count);
         $orders = $query->all();
         $this->assertEquals(3, count($orders));
+
+        // https://github.com/yiisoft/yii2/issues/2880
+        $query = Order::find(1);
+        $customer = $query->getCustomer()->joinWith([
+            'orders' => function ($q) { $q->orderBy([]); }
+        ])->one();
+        $this->assertEquals(1, $customer->id);
+        $order = Order::find()->joinWith([
+            'items' => function ($q) {
+                $q->from(['items' => 'tbl_item'])
+                    ->orderBy('items.id');
+            },
+        ])->orderBy('tbl_order.id')->one();
     }
 
     public function testJoinWithAndScope()
diff --git a/tests/unit/framework/log/TargetTest.php b/tests/unit/framework/log/TargetTest.php
index 035d639..1f2d694 100644
--- a/tests/unit/framework/log/TargetTest.php
+++ b/tests/unit/framework/log/TargetTest.php
@@ -5,6 +5,7 @@
 
 namespace yiiunit\framework\log;
 
+use yii\log\Dispatcher;
 use yii\log\Logger;
 use yii\log\Target;
 use yiiunit\TestCase;
@@ -50,7 +51,9 @@ class TargetTest extends TestCase
     {
         static::$messages = [];
 
-        $logger = new Logger([
+        $logger = new Logger;
+        $dispatcher = new Dispatcher([
+            'logger' => $logger,
             'targets' => [new TestTarget(array_merge($filter, ['logVars' => []]))],
             'flushInterval' => 1,
         ]);