diff --git a/.codeclimate.yml b/.codeclimate.yml
index 614dd1ae2f..d8166d7a26 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -12,9 +12,30 @@ engines:
enabled: true
phpmd:
enabled: true
+ # configure checks, see https://phpmd.org/rules/index.html for details
checks:
- CleanCode/StaticAccess:
- enabled: false
+ # Static access on Yii::$app is normal in Yii
+ CleanCode/StaticAccess:
+ enabled: false
+ # Yii is a framework so if fulfills the job of encapsulating superglobals
+ Controversial/Superglobals:
+ enabled: false
+ # allow private properties to start with $_
+ Controversial/CamelCasePropertyName:
+ enabled: true
+ allow-underscore: true
+ # Short variable names are no problem in most cases, e.g. $n = count(...);
+ Naming/ShortVariable:
+ enabled: false
+ # Long variable names can help with better understanding so we increase the limit a bit
+ Naming/LongVariable:
+ enabled: true
+ maximum: 25
+ # method names like up(), gc(), ... are okay.
+ Naming/ShortMethodName:
+ enabled: true
+ minimum: 2
+
ratings:
paths:
- "**.js"
diff --git a/docs/guide/concept-configurations.md b/docs/guide/concept-configurations.md
index a5f7905061..0afefb2e04 100644
--- a/docs/guide/concept-configurations.md
+++ b/docs/guide/concept-configurations.md
@@ -135,6 +135,28 @@ an [entry script](structure-entry-scripts.md), where the class name is already g
More details about configuring the `components` property of an application can be found
in the [Applications](structure-applications.md) section and the [Service Locator](concept-service-locator.md) section.
+Since version 2.0.11, the application configuration supports [Dependency Injection Container](concept-di-container.md)
+configuration using `container` property. For example:
+
+```php
+$config = [
+ 'id' => 'basic',
+ 'basePath' => dirname(__DIR__),
+ 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
+ 'container' => [
+ 'definitions' => [
+ 'yii\widgets\LinkPager' => ['maxButtonCount' => 5]
+ ],
+ 'singletons' => [
+ // Dependency Injection Container singletons configuration
+ ]
+ ]
+];
+```
+
+To know more about the possible values of `definitions` and `singletons` configuration arrays and real-life examples,
+please read [Advanced Practical Usage](concept-di-container.md#advanced-practical-usage) subsection of the
+[Dependency Injection Container](concept-di-container.md) article.
### Widget Configurations
diff --git a/docs/guide/concept-di-container.md b/docs/guide/concept-di-container.md
index 976730b2af..2b561e5756 100644
--- a/docs/guide/concept-di-container.md
+++ b/docs/guide/concept-di-container.md
@@ -224,11 +224,13 @@ and the container will automatically resolve dependencies by instantiating them
them into the newly created objects. The dependency resolution is recursive, meaning that
if a dependency has other dependencies, those dependencies will also be resolved automatically.
-You can use [[yii\di\Container::get()]] to create new objects. The method takes a dependency name,
-which can be a class name, an interface name or an alias name. The dependency name may or may
-not be registered via `set()` or `setSingleton()`. You may optionally provide a list of class
-constructor parameters and a [configuration](concept-configurations.md) to configure the newly created object.
-For example,
+You can use [[yii\di\Container::get()|get()]] to either create or get object instance.
+The method takes a dependency name, which can be a class name, an interface name or an alias name.
+The dependency name may be registered via [[yii\di\Container::set()|set()]]
+or [[yii\di\Container::setSingleton()|setSingleton()]]. You may optionally provide a list of class
+constructor parameters and a [configuration](concept-configurations.md) to configure the newly created object.
+
+For example:
```php
// "db" is a previously registered alias name
@@ -312,10 +314,10 @@ Yii creates a DI container when you include the `Yii.php` file in the [entry scr
of your application. The DI container is accessible via [[Yii::$container]]. When you call [[Yii::createObject()]],
the method will actually call the container's [[yii\di\Container::get()|get()]] method to create a new object.
As aforementioned, the DI container will automatically resolve the dependencies (if any) and inject them
-into the newly created object. Because Yii uses [[Yii::createObject()]] in most of its core code to create
+into obtained object. Because Yii uses [[Yii::createObject()]] in most of its core code to create
new objects, this means you can customize the objects globally by dealing with [[Yii::$container]].
-For example, you can customize globally the default number of pagination buttons of [[yii\widgets\LinkPager]]:
+For example, let's customize globally the default number of pagination buttons of [[yii\widgets\LinkPager]].
```php
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
@@ -368,6 +370,138 @@ cannot be instantiated. This is because you need to tell the DI container how to
Now if you access the controller again, an instance of `app\components\BookingService` will be
created and injected as the 3rd parameter to the controller's constructor.
+Advanced Practical Usage
+---------------
+
+Say we work on API application and have:
+ - `app\components\Request` class that extends `yii\web\Request` and provides additional functionality
+ - `app\components\Response` class that extends `yii\web\Response` and should have `format` property
+ set to `json` on creation
+ - `app\storage\FileStorage` and `app\storage\DocumentsReader` classes the implement some logic on
+ working with documents that are located in some file storage:
+ ```php
+ class FileStorage
+ {
+ public function __contruct($root) {
+ // whatever
+ }
+ }
+
+ class DocumentsReader
+ {
+ public function __contruct(FileStorage $fs) {
+ // whatever
+ }
+ }
+ ```
+
+It is possible to configure multiple definitions at once, passing configuration array to
+[[yii\di\Container::setDefinitions()|setDefinitions()]] or [[yii\di\Container::setSingletons()|setSingletons()]] method.
+Iterating over the configuration array, the methods will call [[yii\di\Container::set()|set()]]
+or [[yii\di\Container::setSingleton()|setSingleton()]] respectively for each item.
+
+The configuration array format is:
+
+ - `key`: class name, interface name or alias name. The key will be passed to the
+ [[yii\di\Container::set()|set()]] method as a first argument `$class`.
+ - `value`: the definition associated with `$class`. Possible values are described in [[yii\di\Container::set()|set()]]
+ documentation for the `$definition` parameter. Will be passed to the [[set()]] method as
+ the second argument `$definition`.
+
+For example, let's configure our container to follow the aforementioned requirements:
+
+```php
+$container->setDefinitions([
+ 'yii\web\Request' => 'app\components\Request',
+ 'yii\web\Response' => [
+ 'class' => 'app\components\Response',
+ 'format' => 'json'
+ ],
+ 'app\storage\DocumentsReader' => function () {
+ $fs = new app\storage\FileStorage('/var/tempfiles');
+ return new app\storage\DocumentsReader($fs);
+ }
+]);
+
+$reader = $container->get('app\storage\DocumentsReader);
+// Will create DocumentReader object with its dependencies as described in the config
+```
+
+> Tip: Container may be configured in declarative style using application configuration since version 2.0.11.
+Check out the [Application Configurations](concept-service-locator.md#application-configurations) subsection of
+the [Configurations](concept-configurations.md) guide article.
+
+Everything works, but in case we need to create create `DocumentWriter` class,
+we shall copy-paste the line that creates `FileStorage` object, that is not the smartest way, obviously.
+
+As described in the [Resolving Dependencies](#resolving-dependencies) subsection, [[yii\di\Container::set()|set()]]
+and [[yii\di\Container::setSingleton()|setSingleton()]] can optionally take dependency's constructor parameters as
+a third argument. To set the constructor parameters, you may use the following configuration array format:
+
+ - `key`: class name, interface name or alias name. The key will be passed to the
+ [[yii\di\Container::set()|set()]] method as a first argument `$class`.
+ - `value`: array of two elements. The first element will be passed the [[yii\di\Container::set()|set()]] method as the
+ second argument `$definition`, the second one — as `$params`.
+
+Let's modify our example:
+
+```php
+$container->setDefinitions([
+ 'tempFileStorage' => [ // we've created an alias for convenience
+ ['class' => 'app\storage\FileStorage'],
+ ['/var/tempfiles'] // could be extracted from some config files
+ ],
+ 'app\storage\DocumentsReader' => [
+ ['class' => 'app\storage\DocumentsReader'],
+ [Instance::of('tempFileStorage')]
+ ],
+ 'app\storage\DocumentsWriter' => [
+ ['class' => 'app\storage\DocumentsWriter'],
+ [Instance::of('tempFileStorage')]
+ ]
+]);
+
+$reader = $container->get('app\storage\DocumentsReader);
+// Will behave exactly the same as in the previous example.
+```
+
+You might notice `Instance::of('tempFileStorage')` notation. It means, that the [[yii\di\Container|Container]]
+will implicitly provide dependency, registered with `tempFileStorage` name and pass it as the first argument
+of `app\storage\DocumentsWriter` constructor.
+
+> Note: [[yii\di\Container::setDefinitions()|setDefinitions()]] and [[yii\di\Container::setSingletons()|setSingletons()]]
+ methods are available since version 2.0.11.
+
+Another step on configuration optimization is to register some dependencies as singletons.
+A dependency registered via [[yii\di\Container::set()|set()]] will be instantiated each time it is needed.
+Some classes do not change the state during runtime, therefore they may be registered as singletons
+in order to increase the application performance.
+
+A good example could be `app\storage\FileStorage` class, that executes some operations on file system with a simple
+API (e.g. `$fs->read()`, `$fs->write()`). These operations do not change the internal class state, so we can
+create its instance once and use it multiple times.
+
+```php
+$container->setSingletons([
+ 'tempFileStorage' => [
+ ['class' => 'app\storage\FileStorage'],
+ ['/var/tempfiles']
+ ],
+]);
+
+$container->setDefinitions([
+ 'app\storage\DocumentsReader' => [
+ ['class' => 'app\storage\DocumentsReader'],
+ [Instance::of('tempFileStorage')]
+ ],
+ 'app\storage\DocumentsWriter' => [
+ ['class' => 'app\storage\DocumentsWriter'],
+ [Instance::of('tempFileStorage')]
+ ]
+]);
+
+$reader = $container->get('app\storage\DocumentsReader);
+```
When to Register Dependencies
-----------------------------
@@ -375,8 +509,9 @@ When to Register Dependencies
Because dependencies are needed when new objects are being created, their registration should be done
as early as possible. The following are the recommended practices:
-* If you are the developer of an application, you can register dependencies in your
- application's [entry script](structure-entry-scripts.md) or in a script that is included by the entry script.
+* If you are the developer of an application, you can register your dependencies using application configuration.
+ Please, read the [Application Configurations](concept-service-locator.md#application-configurations) subsection of
+ the [Configurations](concept-configurations.md) guide article.
* If you are the developer of a redistributable [extension](structure-extensions.md), you can register dependencies
in the bootstrapping class of the extension.
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index ad82444bfe..4a7969652e 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -27,7 +27,9 @@ Yii Framework 2 Change Log
- Bug #13071: Help option for commands was not working in modules (arogachev, haimanman)
- Bug #13089: Fixed `yii\console\controllers\AssetController::adjustCssUrl()` breaks URL reference specification (`url(#id)`) (vitalyzhakov)
- Bug #7727: Fixed truncateHtml leaving extra tags (developeruz)
-- Enh #6809: Added `\yii\caching\Cache::$defaultDuration` property, allowing to set custom default cache duration (sdkiller)
+- Bug #13118: Fixed `handleAction()` function in `yii.js` to handle attribute `data-pjax=0` as disabled PJAX (silverfire)
+- Enh #6373: Introduce `yii\db\Query::emulateExecution()` to force returning an empty result for a query (klimov-paul)
+- Enh #6809: Added `yii\caching\Cache::$defaultDuration` property, allowing to set custom default cache duration (sdkiller)
- Enh #7333: Improved error message for `yii\di\Instance::ensure()` when a component does not exist (cebe)
- Enh #7420: Attributes for prompt generated with `renderSelectOptions` of `\yii\helpers\Html` helper (arogachev)
- Enh #9162: Added support of closures in `value` for attributes in `yii\widgets\DetailView` (arogachev)
@@ -37,6 +39,7 @@ Yii Framework 2 Change Log
- Enh #11929: Changed `type` column type from `int` to `smallInt` in RBAC migrations (silverfire)
- Enh #12015: Changed visibility `yii\db\ActiveQueryTrait::createModels()` from private to protected (ArekX, dynasource)
- Enh #12399: Added `ActiveField::addAriaAttributes` property for `aria-required` and `aria-invalid` attributes rendering (Oxyaction, samdark)
+- Enh #12390: Avoid creating queries with false where contdition (`0=1`) when fetching relational data (klimov-paul)
- Enh #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()` (rob006)
- Enh #12726: `yii\base\Application::$version` converted to `yii\base\Module::$version` virtual property, allowing to specify version as a PHP callback (klimov-paul)
- Enh #12738: Added support for creating protocol-relative URLs in `UrlManager::createAbsoluteUrl()` and `Url` helper methods (rob006)
@@ -53,8 +56,9 @@ Yii Framework 2 Change Log
- Enh #13036: Added shortcut methods `asJson()` and `asXml()` for returning JSON and XML data in web controller actions (cebe)
- Enh #13020: Added `disabledListItemSubTagOptions` attribute for `yii\widgets\LinkPager` in order to customize the disabled list item sub tag element (nadar)
- Enh #12988: Changed `textarea` method within the `yii\helpers\BaseHtml` class to allow users to control whether html entities found within `$value` will be double-encoded or not (cyphix333)
-- Enh #13074: Improved `\yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
+- Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
- Enh #13050: Added `yii\filters\HostControl` allowing protection against 'host header' attacks (klimov-paul)
+- Enh #11758: Implemented Dependency Injection Container configuration using Application configuration array (silverfire)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
- Enh #12854: Added `RangeNotSatisfiableHttpException` to cover HTTP error 416 file request exceptions (zalatov)
diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md
index d91e00d9d1..7c67fd70a2 100644
--- a/framework/UPGRADE.md
+++ b/framework/UPGRADE.md
@@ -50,6 +50,12 @@ if you want to upgrade from version A to version C and there is
version B between A and C, you need to follow the instructions
for both A and B.
+Upgrade from Yii 2.0.10
+-----------------------
+
+* A new method `public function emulateExecution($value = true);` has been added to the `yii\db\QueryInterace`.
+ This method is implemented in the `yii\db\QueryTrait`, so this only affects your code if you implement QueryInterface
+ in a class that does not use the trait.
Upgrade from Yii 2.0.9
----------------------
diff --git a/framework/assets/yii.js b/framework/assets/yii.js
index e2db617a38..372ae45ae4 100644
--- a/framework/assets/yii.js
+++ b/framework/assets/yii.js
@@ -153,7 +153,7 @@ window.yii = (function ($) {
method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'),
action = $e.attr('href'),
params = $e.data('params'),
- pjax = $e.data('pjax'),
+ pjax = $e.data('pjax') || 0,
pjaxPushState = !!$e.data('pjax-push-state'),
pjaxReplaceState = !!$e.data('pjax-replace-state'),
pjaxTimeout = $e.data('pjax-timeout'),
@@ -164,7 +164,7 @@ window.yii = (function ($) {
pjaxContainer,
pjaxOptions = {};
- if (pjax !== undefined && $.support.pjax) {
+ if (pjax !== 0 && $.support.pjax) {
if ($e.data('pjax-container')) {
pjaxContainer = $e.data('pjax-container');
} else {
@@ -190,13 +190,13 @@ window.yii = (function ($) {
if (method === undefined) {
if (action && action != '#') {
- if (pjax !== undefined && $.support.pjax) {
+ if (pjax !== 0 && $.support.pjax) {
$.pjax.click(event, pjaxOptions);
} else {
window.location = action;
}
} else if ($e.is(':submit') && $form.length) {
- if (pjax !== undefined && $.support.pjax) {
+ if (pjax !== 0 && $.support.pjax) {
$form.on('submit',function(e){
$.pjax.submit(e, pjaxOptions);
})
@@ -249,7 +249,7 @@ window.yii = (function ($) {
oldAction = $form.attr('action');
$form.attr('action', action);
}
- if (pjax !== undefined && $.support.pjax) {
+ if (pjax !== 0 && $.support.pjax) {
$form.on('submit',function(e){
$.pjax.submit(e, pjaxOptions);
})
diff --git a/framework/base/Application.php b/framework/base/Application.php
index f31bfd1d27..03688807d8 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -246,6 +246,12 @@ abstract class Application extends Module
$this->setTimeZone('UTC');
}
+ if (isset($config['container'])) {
+ $this->setContainer($config['container']);
+
+ unset($config['container']);
+ }
+
// merge core components with custom components
foreach ($this->coreComponents() as $id => $component) {
if (!isset($config['components'][$id])) {
@@ -652,4 +658,15 @@ abstract class Application extends Module
exit($status);
}
}
+
+ /**
+ * Configures [[Yii::$container]] with the $config
+ *
+ * @param array $config values given in terms of name-value pairs
+ * @since 2.0.11
+ */
+ public function setContainer($config)
+ {
+ Yii::configure(Yii::$container, $config);
+ }
}
diff --git a/framework/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php
index b8c5241160..4d9caf3d4f 100644
--- a/framework/db/ActiveRelationTrait.php
+++ b/framework/db/ActiveRelationTrait.php
@@ -464,6 +464,9 @@ trait ActiveRelationTrait
}
}
}
+ if (empty($values)) {
+ $this->emulateExecution();
+ }
} else {
// composite keys
@@ -478,6 +481,9 @@ trait ActiveRelationTrait
$v[$attribute] = $model[$link];
}
$values[] = $v;
+ if (empty($v)) {
+ $this->emulateExecution();
+ }
}
}
$this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);
diff --git a/framework/db/Query.php b/framework/db/Query.php
index 6fe9bfc9f9..1bb831da0d 100644
--- a/framework/db/Query.php
+++ b/framework/db/Query.php
@@ -207,6 +207,9 @@ class Query extends Component implements QueryInterface
*/
public function all($db = null)
{
+ if ($this->emulateExecution) {
+ return [];
+ }
$rows = $this->createCommand($db)->queryAll();
return $this->populate($rows);
}
@@ -244,6 +247,9 @@ class Query extends Component implements QueryInterface
*/
public function one($db = null)
{
+ if ($this->emulateExecution) {
+ return false;
+ }
return $this->createCommand($db)->queryOne();
}
@@ -257,6 +263,9 @@ class Query extends Component implements QueryInterface
*/
public function scalar($db = null)
{
+ if ($this->emulateExecution) {
+ return null;
+ }
return $this->createCommand($db)->queryScalar();
}
@@ -268,6 +277,10 @@ class Query extends Component implements QueryInterface
*/
public function column($db = null)
{
+ if ($this->emulateExecution) {
+ return [];
+ }
+
if ($this->indexBy === null) {
return $this->createCommand($db)->queryColumn();
}
@@ -300,6 +313,9 @@ class Query extends Component implements QueryInterface
*/
public function count($q = '*', $db = null)
{
+ if ($this->emulateExecution) {
+ return 0;
+ }
return $this->queryScalar("COUNT($q)", $db);
}
@@ -313,6 +329,9 @@ class Query extends Component implements QueryInterface
*/
public function sum($q, $db = null)
{
+ if ($this->emulateExecution) {
+ return 0;
+ }
return $this->queryScalar("SUM($q)", $db);
}
@@ -326,6 +345,9 @@ class Query extends Component implements QueryInterface
*/
public function average($q, $db = null)
{
+ if ($this->emulateExecution) {
+ return 0;
+ }
return $this->queryScalar("AVG($q)", $db);
}
@@ -363,6 +385,9 @@ class Query extends Component implements QueryInterface
*/
public function exists($db = null)
{
+ if ($this->emulateExecution) {
+ return false;
+ }
$command = $this->createCommand($db);
$params = $command->params;
$command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql()));
@@ -379,6 +404,10 @@ class Query extends Component implements QueryInterface
*/
protected function queryScalar($selectExpression, $db)
{
+ if ($this->emulateExecution) {
+ return null;
+ }
+
$select = $this->select;
$limit = $this->limit;
$offset = $this->offset;
diff --git a/framework/db/QueryInterface.php b/framework/db/QueryInterface.php
index 28d16c9e1c..7503fa3664 100644
--- a/framework/db/QueryInterface.php
+++ b/framework/db/QueryInterface.php
@@ -252,4 +252,16 @@ interface QueryInterface
* @return $this the query object itself
*/
public function offset($offset);
+
+ /**
+ * Sets whether to emulate query execution, preventing any interaction with data storage.
+ * After this mode is enabled, methods, returning query results like [[one()]], [[all()]], [[exists()]]
+ * and so on, will return empty or false values.
+ * You should use this method in case your program logic indicates query should not return any results, like
+ * in case you set false where condition like `0=1`.
+ * @param boolean $value whether to prevent query execution.
+ * @return $this the query object itself.
+ * @since 2.0.11
+ */
+ public function emulateExecution($value = true);
}
diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php
index 4a64903d0b..67cd2bf215 100644
--- a/framework/db/QueryTrait.php
+++ b/framework/db/QueryTrait.php
@@ -50,6 +50,12 @@ trait QueryTrait
* row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
*/
public $indexBy;
+ /**
+ * @var boolean whether to emulate the actual query execution, returning empty or false results.
+ * @see emulateExecution()
+ * @since 2.0.11
+ */
+ public $emulateExecution = false;
/**
@@ -388,4 +394,20 @@ trait QueryTrait
$this->offset = $offset;
return $this;
}
+
+ /**
+ * Sets whether to emulate query execution, preventing any interaction with data storage.
+ * After this mode is enabled, methods, returning query results like [[one()]], [[all()]], [[exists()]]
+ * and so on, will return empty or false values.
+ * You should use this method in case your program logic indicates query should not return any results, like
+ * in case you set false where condition like `0=1`.
+ * @param boolean $value whether to prevent query execution.
+ * @return $this the query object itself.
+ * @since 2.0.11
+ */
+ public function emulateExecution($value = true)
+ {
+ $this->emulateExecution = $value;
+ return $this;
+ }
}
diff --git a/framework/di/Container.php b/framework/di/Container.php
index 3649d6fe9c..5446126d43 100644
--- a/framework/di/Container.php
+++ b/framework/di/Container.php
@@ -566,4 +566,84 @@ class Container extends Component
}
return $args;
}
+
+ /**
+ * Registers class definitions within this container.
+ *
+ * @param array $definitions array of definitions. There are two allowed formats of array.
+ * The first format:
+ * - key: class name, interface name or alias name. The key will be passed to the [[set()]] method
+ * as a first argument `$class`.
+ * - value: the definition associated with `$class`. Possible values are described in
+ * [[set()]] documentation for the `$definition` parameter. Will be passed to the [[set()]] method
+ * as the second argument `$definition`.
+ *
+ * Example:
+ * ```php
+ * $container->setDefinitions([
+ * 'yii\web\Request' => 'app\components\Request',
+ * 'yii\web\Response' => [
+ * 'class' => 'app\components\Response',
+ * 'format' => 'json'
+ * ],
+ * 'foo\Bar' => function () {
+ * $qux = new Qux;
+ * $foo = new Foo($qux);
+ * return new Bar($foo);
+ * }
+ * ]);
+ * ```
+ *
+ * The second format:
+ * - key: class name, interface name or alias name. The key will be passed to the [[set()]] method
+ * as a first argument `$class`.
+ * - value: array of two elements. The first element will be passed the [[set()]] method as the
+ * second argument `$definition`, the second one — as `$params`.
+ *
+ * Example:
+ * ```php
+ * $container->setDefinitions([
+ * 'foo\Bar' => [
+ * ['class' => 'app\Bar'],
+ * [Instance::of('baz')]
+ * ]
+ * ]);
+ * ```
+ *
+ * @see set() to know more about possible values of definitions
+ * @since 2.0.11
+ */
+ public function setDefinitions(array $definitions)
+ {
+ foreach ($definitions as $class => $definition) {
+ if (count($definition) === 2 && array_values($definition) === $definition) {
+ $this->set($class, $definition[0], $definition[1]);
+ continue;
+ }
+
+ $this->set($class, $definition);
+ }
+ }
+
+ /**
+ * Registers class definitions as singletons within this container by calling [[setSingleton()]]
+ *
+ * @param array $singletons array of singleton definitions. See [[setDefinitions()]]
+ * for allowed formats of array.
+ *
+ * @see setDefinitions() for allowed formats of $singletons parameter
+ * @see setSingleton() to know more about possible values of definitions
+ * @since 2.0.11
+ */
+ public function setSingletons(array $singletons)
+ {
+ foreach ($singletons as $class => $definition) {
+ if (count($definition) === 2 && array_values($definition) === $definition) {
+ $this->setSingleton($class, $definition[0], $definition[1]);
+ continue;
+ }
+
+ $this->setSingleton($class, $definition);
+ }
+ }
}
diff --git a/tests/framework/base/ApplicationTest.php b/tests/framework/base/ApplicationTest.php
new file mode 100644
index 0000000000..2be0858d93
--- /dev/null
+++ b/tests/framework/base/ApplicationTest.php
@@ -0,0 +1,32 @@
+mockApplication([
+ 'container' => [
+ 'definitions' => [
+ Dispatcher::className() => DispatcherMock::className()
+ ]
+ ],
+ 'bootstrap' => ['log']
+ ]);
+
+ $this->assertInstanceOf(DispatcherMock::className(), Yii::$app->log);
+ }
+}
+
+class DispatcherMock extends Dispatcher
+{
+
+}
diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php
index 709757bf7b..8137243752 100644
--- a/tests/framework/db/ActiveRecordTest.php
+++ b/tests/framework/db/ActiveRecordTest.php
@@ -1294,4 +1294,70 @@ abstract class ActiveRecordTest extends DatabaseTestCase
$this->assertEquals($newTotal, $newOrder->total);
}
+ public function testEmulateExecution()
+ {
+ $this->assertGreaterThan(0, Customer::find()->from('customer')->count());
+
+ $rows = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->all();
+ $this->assertSame([], $rows);
+
+ $row = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->one();
+ $this->assertSame(null, $row);
+
+ $exists = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->exists();
+ $this->assertSame(false, $exists);
+
+ $count = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->count();
+ $this->assertSame(0, $count);
+
+ $sum = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->sum('id');
+ $this->assertSame(0, $sum);
+
+ $sum = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->average('id');
+ $this->assertSame(0, $sum);
+
+ $max = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->max('id');
+ $this->assertSame(null, $max);
+
+ $min = Customer::find()
+ ->from('customer')
+ ->emulateExecution()
+ ->min('id');
+ $this->assertSame(null, $min);
+
+ $scalar = Customer::find()
+ ->select(['id'])
+ ->from('customer')
+ ->emulateExecution()
+ ->scalar();
+ $this->assertSame(null, $scalar);
+
+ $column = Customer::find()
+ ->select(['id'])
+ ->from('customer')
+ ->emulateExecution()
+ ->column();
+ $this->assertSame([], $column);
+ }
}
diff --git a/tests/framework/db/QueryTest.php b/tests/framework/db/QueryTest.php
index a5a68f1420..005f9a0c85 100644
--- a/tests/framework/db/QueryTest.php
+++ b/tests/framework/db/QueryTest.php
@@ -2,6 +2,7 @@
namespace yiiunit\framework\db;
+use yii\db\Connection;
use yii\db\Expression;
use yii\db\Query;
@@ -317,4 +318,73 @@ abstract class QueryTest extends DatabaseTestCase
$count = (new Query)->from('customer')->having(['status' => 2])->count('*', $db);
$this->assertEquals(1, $count);
}
+
+ public function testEmulateExecution()
+ {
+ $db = $this->getConnection();
+
+ $this->assertGreaterThan(0, (new Query())->from('customer')->count('*', $db));
+
+ $rows = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->all($db);
+ $this->assertSame([], $rows);
+
+ $row = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->one($db);
+ $this->assertSame(false, $row);
+
+ $exists = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->exists($db);
+ $this->assertSame(false, $exists);
+
+ $count = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->count('*', $db);
+ $this->assertSame(0, $count);
+
+ $sum = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->sum('id', $db);
+ $this->assertSame(0, $sum);
+
+ $sum = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->average('id', $db);
+ $this->assertSame(0, $sum);
+
+ $max = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->max('id', $db);
+ $this->assertSame(null, $max);
+
+ $min = (new Query())
+ ->from('customer')
+ ->emulateExecution()
+ ->min('id', $db);
+ $this->assertSame(null, $min);
+
+ $scalar = (new Query())
+ ->select(['id'])
+ ->from('customer')
+ ->emulateExecution()
+ ->scalar($db);
+ $this->assertSame(null, $scalar);
+
+ $column = (new Query())
+ ->select(['id'])
+ ->from('customer')
+ ->emulateExecution()
+ ->column($db);
+ $this->assertSame([], $column);
+ }
}
diff --git a/tests/framework/di/ContainerTest.php b/tests/framework/di/ContainerTest.php
index c6ac6db1ad..fe632a8b78 100644
--- a/tests/framework/di/ContainerTest.php
+++ b/tests/framework/di/ContainerTest.php
@@ -10,6 +10,9 @@ namespace yiiunit\framework\di;
use Yii;
use yii\di\Container;
use yii\di\Instance;
+use yiiunit\data\ar\Cat;
+use yiiunit\data\ar\Order;
+use yiiunit\data\ar\Type;
use yiiunit\framework\di\stubs\Bar;
use yiiunit\framework\di\stubs\Foo;
use yiiunit\framework\di\stubs\Qux;
@@ -223,4 +226,58 @@ class ContainerTest extends TestCase
};
$this->assertNull($container->invoke($closure));
}
+
+ public function testSetDependencies()
+ {
+ $container = new Container();
+ $container->setDefinitions([
+ 'model.order' => Order::className(),
+ Cat::className() => Type::className(),
+ 'test\TraversableInterface' => [
+ ['class' => 'yiiunit\data\base\TraversableObject'],
+ [['item1', 'item2']]
+ ],
+ 'qux.using.closure' => function () {
+ return new Qux();
+ }
+ ]);
+ $container->setDefinitions([]);
+
+ $this->assertInstanceOf(Order::className(), $container->get('model.order'));
+ $this->assertInstanceOf(Type::className(), $container->get(Cat::className()));
+
+ $traversable = $container->get('test\TraversableInterface');
+ $this->assertInstanceOf('yiiunit\data\base\TraversableObject', $traversable);
+ $this->assertEquals('item1', $traversable->current());
+
+ $this->assertInstanceOf('yiiunit\framework\di\stubs\Qux', $container->get('qux.using.closure'));
+ }
+
+ public function testContainerSingletons()
+ {
+ $container = new Container();
+ $container->setSingletons([
+ 'model.order' => Order::className(),
+ 'test\TraversableInterface' => [
+ ['class' => 'yiiunit\data\base\TraversableObject'],
+ [['item1', 'item2']]
+ ],
+ 'qux.using.closure' => function () {
+ return new Qux();
+ }
+ ]);
+ $container->setSingletons([]);
+
+ $order = $container->get('model.order');
+ $sameOrder = $container->get('model.order');
+ $this->assertSame($order, $sameOrder);
+
+ $traversable = $container->get('test\TraversableInterface');
+ $sameTraversable = $container->get('test\TraversableInterface');
+ $this->assertSame($traversable, $sameTraversable);
+
+ $foo = $container->get('qux.using.closure');
+ $sameFoo = $container->get('qux.using.closure');
+ $this->assertSame($foo, $sameFoo);
+ }
}