mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-02 13:02:24 +08:00
Merge branch 'master' into aria-required-and-aria-invalid-input-attributes
This commit is contained in:
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
----------------------
|
||||
|
||||
@ -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);
|
||||
})
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
tests/framework/base/ApplicationTest.php
Normal file
32
tests/framework/base/ApplicationTest.php
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user