Merge branch 'master' into aria-required-and-aria-invalid-input-attributes

This commit is contained in:
Alexander Makarov
2016-12-02 23:38:55 +03:00
16 changed files with 597 additions and 18 deletions

View File

@ -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"

View File

@ -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 <span id="widget-configurations"></span>

View File

@ -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 <span id="advanced-practical-usage"></span>
---------------
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 <span id="when-to-register-dependencies"></span>
-----------------------------
@ -375,8 +509,9 @@ When to Register Dependencies <span id="when-to-register-dependencies"></span>
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.

View File

@ -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)

View File

@ -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
----------------------

View File

@ -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);
})

View File

@ -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);
}
}

View File

@ -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)]);

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace yiiunit\framework\base;
use Yii;
use yii\log\Dispatcher;
use yiiunit\data\ar\Cat;
use yiiunit\data\ar\Order;
use yiiunit\data\ar\Type;
use yiiunit\TestCase;
class ApplicationTest extends TestCase
{
public function testContainerSettingsAffectBootstrap()
{
$this->mockApplication([
'container' => [
'definitions' => [
Dispatcher::className() => DispatcherMock::className()
]
],
'bootstrap' => ['log']
]);
$this->assertInstanceOf(DispatcherMock::className(), Yii::$app->log);
}
}
class DispatcherMock extends Dispatcher
{
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}