Commit 7939a3de by Carsten Brandt

Merge branch 'master' into array-attribute-relations

* master: Update output-data-widgets.md Fixes #4370: fileinfo extension is required for FileValidator. Fixes #4342 Fixes #3887 minor refactoring of FileValidator. Doc translate for rest-resources Better fix for Dropdown container options #4388: CORS filter fixes: Fixed test break. #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` Update .travis.yml Update ActiveRecordInterface.php Fixes #4384. Sphinx distributed indexes support provided typo fix [skip ci] Fixes #3611: Refactored query caching.
parents 5ad178a6 92e131c5
...@@ -31,7 +31,7 @@ install: ...@@ -31,7 +31,7 @@ install:
- tests/unit/data/travis/memcache-setup.sh - tests/unit/data/travis/memcache-setup.sh
- tests/unit/data/travis/cubrid-setup.sh - tests/unit/data/travis/cubrid-setup.sh
# basic and advanced application: # basic and advanced application:
- tests/unit/data/travis/setup-apps.sh # - tests/unit/data/travis/setup-apps.sh
before_script: before_script:
- echo 'elasticsearch version ' && curl http://localhost:9200/ - echo 'elasticsearch version ' && curl http://localhost:9200/
...@@ -40,16 +40,16 @@ before_script: ...@@ -40,16 +40,16 @@ before_script:
- tests/unit/data/travis/sphinx-setup.sh - tests/unit/data/travis/sphinx-setup.sh
- mongo yii2test --eval 'db.addUser("travis", "test");' - mongo yii2test --eval 'db.addUser("travis", "test");'
# basic and advanced application: # basic and advanced application:
- tests/unit/data/travis/init-apps.sh # - tests/unit/data/travis/init-apps.sh
script: script:
- vendor/bin/phpunit --verbose --coverage-clover=coverage.clover --exclude-group mssql,oci,wincache,xcache,zenddata - vendor/bin/phpunit --verbose --coverage-clover=coverage.clover --exclude-group mssql,oci,wincache,xcache,zenddata
- cd apps/basic && php vendor/bin/codecept run # - cd apps/basic && php vendor/bin/codecept run
- cd ../advanced/backend && ../vendor/bin/codecept run # - cd ../advanced/backend && ../vendor/bin/codecept run
- cd ../common && ../vendor/bin/codecept run # - cd ../common && ../vendor/bin/codecept run
- cd ../frontend && ../vendor/bin/codecept run # - cd ../frontend && ../vendor/bin/codecept run
after_script: after_script:
- cd ../../.. # - cd ../../..
- wget https://scrutinizer-ci.com/ocular.phar - wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
...@@ -245,20 +245,15 @@ Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache` ...@@ -245,20 +245,15 @@ Query caching requires a [[yii\db\Connection|DB connection]] and a valid `cache`
The basic usage of query caching is as follows, assuming `$db` is a [[yii\db\Connection]] instance: The basic usage of query caching is as follows, assuming `$db` is a [[yii\db\Connection]] instance:
```php ```php
$duration = 60; // cache query results for 60 seconds. $result = $db->cache(function ($db) {
$dependency = ...; // optional dependency
$db->beginCache($duration, $dependency);
// ...performs DB queries here... // the result of the SQL query will be served from the cache
// if query caching is enabled and the query result is found in the cache
return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
$db->endCache(); });
``` ```
As you can see, any SQL queries in between the `beginCache()` and `endCache()` calls will be cached.
If the result of the same query is found valid in the cache, the query will be skipped and the result
will be served from the cache instead.
Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-active-record.md). Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-active-record.md).
> Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html)) > Info: Some DBMS (e.g. [MySQL](http://dev.mysql.com/doc/refman/5.1/en/query-cache.html))
...@@ -269,13 +264,85 @@ Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-acti ...@@ -269,13 +264,85 @@ Query caching can be used for [DAO](db-dao.md) as well as [ActiveRecord](db-acti
### Configurations <a name="query-caching-configs"></a> ### Configurations <a name="query-caching-configs"></a>
Query caching has two configurable options through [[yii\db\Connection]]: Query caching has three global configurable options through [[yii\db\Connection]]:
* [[yii\db\Connection::enableQueryCache|enableQueryCache]]: whether to turn on or off query caching.
It defaults to true. Note that to effectively turn on query caching, you also need to have a valid
cache, as specified by [[yii\db\Connection::queryCache|queryCache]].
* [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: this represents the number of seconds * [[yii\db\Connection::queryCacheDuration|queryCacheDuration]]: this represents the number of seconds
that a query result can remain valid in the cache. The duration will be overwritten if you call that a query result can remain valid in the cache. You can use 0 to indicate a query result should
[[yii\db\Connection::beginCache()]] with an explicit duration parameter. remain in the cache forever. This property is the default value used when [[yii\db\Connection::cache()]]
is called without specifying a duration.
* [[yii\db\Connection::queryCache|queryCache]]: this represents the ID of the cache application component. * [[yii\db\Connection::queryCache|queryCache]]: this represents the ID of the cache application component.
It defaults to `'cache'`. Query caching is enabled only when there is a valid cache application component. It defaults to `'cache'`. Query caching is enabled only if there is a valid cache application component.
### Usages <a name="query-caching-usages"></a>
You can use [[yii\db\Connection::cache()]] if you have multiple SQL queries that need to take advantage of
query caching. The usage is as follows,
```php
$duration = 60; // cache query results for 60 seconds.
$dependency = ...; // optional dependency
$result = $db->cache(function ($db) {
// ... perform SQL queries here ...
return $result;
}, $duration, $dependency);
```
Any SQL queries in the anonymous function will be cached for the specified duration with the specified dependency.
If the result of a query is found valid in the cache, the query will be skipped and the result will be served
from the cache instead. If you do not specify the `$duration` parameter, the value of
[[yii\db\Connection::queryCacheDuration|queryCacheDuration]] will be used instead.
Sometimes within `cache()`, you may want to disable query caching for some particular queries. You can use
[[yii\db\Connection::noCache()]] in this case.
```php
$result = $db->cache(function ($db) {
// SQL queries that use query caching
$db->noCache(function ($db) {
// SQL queries that do not use query caching
});
// ...
return $result;
});
```
If you just want to use query caching for a single query, you can call [[yii\db\Command::cache()]] when building
the command. For example,
```php
// use query caching and set query cache duration to be 60 seconds
$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->cache(60)->queryOne();
```
You can also use [[yii\db\Command::noCache()]] to disable query caching for a single command. For example,
```php
$result = $db->cache(function ($db) {
// SQL queries that use query caching
// do not use query caching for this command
$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->noCache()->queryOne();
// ...
return $result;
});
```
### Limitations <a name="query-caching-limitations"></a> ### Limitations <a name="query-caching-limitations"></a>
......
...@@ -381,6 +381,7 @@ $query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name' ...@@ -381,6 +381,7 @@ $query->andFilterWhere(['LIKE', 'author.name', $this->getAttribute('author.name'
> ``` > ```
> >
> Same is true for the sorting definition: > Same is true for the sorting definition:
>
> ```php > ```php
> $dataProvider->sort->attributes['author.name'] = [ > $dataProvider->sort->attributes['author.name'] = [
> 'asc' => ['au.name' => SORT_ASC], > 'asc' => ['au.name' => SORT_ASC],
......
...@@ -346,10 +346,10 @@ The Cors filtering could be tuned using the `cors` property. ...@@ -346,10 +346,10 @@ The Cors filtering could be tuned using the `cors` property.
* `cors['Origin']`: array used to define allowed origins. Can be `['*']` (everyone) or `['http://www.myserver.net', 'http://www.myotherserver.com']`. Default to `['*']`. * `cors['Origin']`: array used to define allowed origins. Can be `['*']` (everyone) or `['http://www.myserver.net', 'http://www.myotherserver.com']`. Default to `['*']`.
* `cors['Access-Control-Request-Method']`: array of allowed verbs like `['GET', 'OPTIONS', 'HEAD']`. Default to `['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']`. * `cors['Access-Control-Request-Method']`: array of allowed verbs like `['GET', 'OPTIONS', 'HEAD']`. Default to `['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']`.
* `cors['Access-Control-Request-Headers']`: array of allowed headers. Can be `['*']` all headers or specific ones `['X-Request-With']`. Default to `['*']`. * `cors['Access-Control-Request-Headers']`: array of allowed headers. Can be `['*']` all headers or specific ones `['X-Request-With']`. Default to `['*']`.
* `cors['Access-Control-Allow-Credentials']`: define if current request can be made using credentials. Can be `true`, `false`. Default to `true`. * `cors['Access-Control-Allow-Credentials']`: define if current request can be made using credentials. Can be `true`, `false` or `null` (not set). Default to `null`.
* `cors['Access-Control-Max-Age']`: define lifetime of pre-flight request. Default to `86400`. * `cors['Access-Control-Max-Age']`: define lifetime of pre-flight request. Default to `86400`.
For example, allowing CORS for origin : `http://www.myserver.net` with method `GET`, `HEAD` and `OPTIONS` and do not send `Access-Control-Allow-Credentials` header : For example, allowing CORS for origin : `http://www.myserver.net` with method `GET`, `HEAD` and `OPTIONS` :
```php ```php
use yii\filters\Cors; use yii\filters\Cors;
...@@ -363,7 +363,6 @@ public function behaviors() ...@@ -363,7 +363,6 @@ public function behaviors()
'cors' => [ 'cors' => [
'Origin' => ['http://www.myserver.net'], 'Origin' => ['http://www.myserver.net'],
'Access-Control-Request-Method' => ['GET', 'HEAD', 'OPTIONS'], 'Access-Control-Request-Method' => ['GET', 'HEAD', 'OPTIONS'],
'Access-Control-Allow-Credentials' => null,
], ],
], ],
], parent::behaviors()); ], parent::behaviors());
...@@ -385,7 +384,6 @@ public function behaviors() ...@@ -385,7 +384,6 @@ public function behaviors()
'cors' => [ 'cors' => [
'Origin' => ['http://www.myserver.net'], 'Origin' => ['http://www.myserver.net'],
'Access-Control-Request-Method' => ['GET', 'HEAD', 'OPTIONS'], 'Access-Control-Request-Method' => ['GET', 'HEAD', 'OPTIONS'],
'Access-Control-Allow-Credentials' => null,
], ],
'actions' => [ 'actions' => [
'login' => [ 'login' => [
......
...@@ -40,6 +40,11 @@ class Dropdown extends Widget ...@@ -40,6 +40,11 @@ class Dropdown extends Widget
*/ */
public $encodeLabels = true; public $encodeLabels = true;
/**
* @var array the HTML attributes for the widget container tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
protected $_containerOptions = [];
/** /**
* Initializes the widget. * Initializes the widget.
...@@ -49,6 +54,7 @@ class Dropdown extends Widget ...@@ -49,6 +54,7 @@ class Dropdown extends Widget
{ {
parent::init(); parent::init();
Html::addCssClass($this->options, 'dropdown-menu'); Html::addCssClass($this->options, 'dropdown-menu');
$this->_containerOptions = $this->options;
} }
/** /**
...@@ -56,17 +62,16 @@ class Dropdown extends Widget ...@@ -56,17 +62,16 @@ class Dropdown extends Widget
*/ */
public function run() public function run()
{ {
echo $this->renderItems($this->items, $this->options); echo $this->renderItems($this->items);
} }
/** /**
* Renders menu items. * Renders menu items.
* @param array $items the menu items to be rendered * @param array $items the menu items to be rendered
* @param array $containerOptions the HTML attributes for the widget container tag
* @return string the rendering result. * @return string the rendering result.
* @throws InvalidConfigException if the label option is not specified in one of the items. * @throws InvalidConfigException if the label option is not specified in one of the items.
*/ */
protected function renderItems($items, $containerOptions) protected function renderItems($items)
{ {
$lines = []; $lines = [];
foreach ($items as $i => $item) { foreach ($items as $i => $item) {
...@@ -88,13 +93,13 @@ class Dropdown extends Widget ...@@ -88,13 +93,13 @@ class Dropdown extends Widget
$linkOptions['tabindex'] = '-1'; $linkOptions['tabindex'] = '-1';
$content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
if (!empty($item['items'])) { if (!empty($item['items'])) {
unset($containerOptions['id']); unset($this->_containerOptions['id']);
$this->renderItems($item['items'], $containerOptions); $this->renderItems($item['items']);
Html::addCssClass($options, 'dropdown-submenu'); Html::addCssClass($options, 'dropdown-submenu');
} }
$lines[] = Html::tag('li', $content, $options); $lines[] = Html::tag('li', $content, $options);
} }
return Html::tag('ul', implode("\n", $lines), $this->options); return Html::tag('ul', implode("\n", $lines), $this->_containerOptions);
} }
} }
...@@ -6,6 +6,7 @@ Yii Framework 2 sphinx extension Change Log ...@@ -6,6 +6,7 @@ Yii Framework 2 sphinx extension Change Log
- Bug #3668: Escaping of the special characters at 'MATCH' statement added (klimov-paul) - Bug #3668: Escaping of the special characters at 'MATCH' statement added (klimov-paul)
- Bug #4018: AR relation eager loading does not work with db models (klimov-paul) - Bug #4018: AR relation eager loading does not work with db models (klimov-paul)
- Bug #4375: Distributed indexes support provided (klimov-paul)
- Enh #3520: Added `unlinkAll()`-method to active record to remove all records of a model relation (NmDimas, samdark, cebe) - Enh #3520: Added `unlinkAll()`-method to active record to remove all records of a model relation (NmDimas, samdark, cebe)
- Enh #4048: Added `init` event to `ActiveQuery` classes (qiangxue) - Enh #4048: Added `init` event to `ActiveQuery` classes (qiangxue)
- Enh #4086: changedAttributes of afterSave Event now contain old values (dizews) - Enh #4086: changedAttributes of afterSave Event now contain old values (dizews)
......
...@@ -459,6 +459,8 @@ class Schema extends Object ...@@ -459,6 +459,8 @@ class Schema extends Object
} }
throw $e; throw $e;
} }
if (empty($columns[0]['Agent'])) {
foreach ($columns as $info) { foreach ($columns as $info) {
$column = $this->loadColumnSchema($info); $column = $this->loadColumnSchema($info);
$index->columns[$column->name] = $column; $index->columns[$column->name] = $column;
...@@ -466,6 +468,11 @@ class Schema extends Object ...@@ -466,6 +468,11 @@ class Schema extends Object
$index->primaryKey = $column->name; $index->primaryKey = $column->name;
} }
} }
} else {
// Distributed index :
$agent = $this->getIndexSchema($columns[0]['Agent']);
$index->columns = $agent->columns;
}
return true; return true;
} }
......
...@@ -68,6 +68,7 @@ Yii Framework 2 Change Log ...@@ -68,6 +68,7 @@ Yii Framework 2 Change Log
- Bug #4162: Fixed bug where schema name was not used in ’SHOW CREATE TABLE’ query in `yii\db\mysql\Schema` (stevekr) - Bug #4162: Fixed bug where schema name was not used in ’SHOW CREATE TABLE’ query in `yii\db\mysql\Schema` (stevekr)
- Bug #4241: `yii\widgets\Pjax` was incorrectly setting container id (mitalcoi) - Bug #4241: `yii\widgets\Pjax` was incorrectly setting container id (mitalcoi)
- Bug #4276: Added check for UPLOAD_ERR_NO_FILE in `yii\web\UploadedFile` and return null if no file was uploaded (OmgDef) - Bug #4276: Added check for UPLOAD_ERR_NO_FILE in `yii\web\UploadedFile` and return null if no file was uploaded (OmgDef)
- Bug #4342: mssql (dblib) driver does not support getting attributes (tof06)
- Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark) - Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark)
- Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul) - Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul)
- Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue) - Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue)
...@@ -168,6 +169,11 @@ Yii Framework 2 Change Log ...@@ -168,6 +169,11 @@ Yii Framework 2 Change Log
- Chg #3383: Added `$type` parameter to `IdentityInterface::findIdentityByAccessToken()` (qiangxue) - Chg #3383: Added `$type` parameter to `IdentityInterface::findIdentityByAccessToken()` (qiangxue)
- Chg #3531: \yii\grid\GridView now allows any character (except ":") in the attribute part of the shorthand syntax for columns (rawtaz) - Chg #3531: \yii\grid\GridView now allows any character (except ":") in the attribute part of the shorthand syntax for columns (rawtaz)
- Chg #3544: Added `$key` as a parameter to the callable specified via `yii\grid\DataColumn::value` (mdmunir) - Chg #3544: Added `$key` as a parameter to the callable specified via `yii\grid\DataColumn::value` (mdmunir)
- Chg #3611: Query caching is refactored. (qiangxue)
- `yii\db\Connection::beginCache()` and `endCache()` are removed.
- Added `yii\db\Connection::cache()` and `noCache()`.
- Added `Command::cache()` and `noCache()`.
- `yii\db\Connection::queryCacheDuration` is now used as a default cache duration parameter.
- Chg #3640: All cookies are now httpOnly by default in order to increase overall security (samdark) - Chg #3640: All cookies are now httpOnly by default in order to increase overall security (samdark)
- Chg #3687: Default `sourceLanguage` and `language` are now `en-US` in order for i18n formatter to work correctly (samdark) - Chg #3687: Default `sourceLanguage` and `language` are now `en-US` in order for i18n formatter to work correctly (samdark)
- Chg #3804: Added `fileinfo` PHP extension to the basic requirement of Yii (Ragazzo) - Chg #3804: Added `fileinfo` PHP extension to the basic requirement of Yii (Ragazzo)
...@@ -181,6 +187,7 @@ Yii Framework 2 Change Log ...@@ -181,6 +187,7 @@ Yii Framework 2 Change Log
- Chg #4147: `BaseMailer::compose()` will not overwrite the `message` parameter if it is explicitly provided (qiangxue) - Chg #4147: `BaseMailer::compose()` will not overwrite the `message` parameter if it is explicitly provided (qiangxue)
- Chg #4201: change default value of `SyslogTarget::facility` from LOG_SYSLOG to LOG_USER (dizews) - Chg #4201: change default value of `SyslogTarget::facility` from LOG_SYSLOG to LOG_USER (dizews)
- Chg #4227: `\yii\widgets\LinkPager::$hideOnSinglePage` is now `true` by default (samdark) - Chg #4227: `\yii\widgets\LinkPager::$hideOnSinglePage` is now `true` by default (samdark)
- Chg #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` (samdark)
- Chg #4318: `yii\helpers\Html::ul()` and `ol()` will return an empty list tag if an empty item array is given (qiangxue) - Chg #4318: `yii\helpers\Html::ul()` and `ol()` will return an empty list tag if an empty item array is given (qiangxue)
- Chg #4331: `yii\helpers\Url` now uses `UrlManager` to determine base URL when generating URLs (qiangxue) - Chg #4331: `yii\helpers\Url` now uses `UrlManager` to determine base URL when generating URLs (qiangxue)
- Chg: Replaced `clearAll()` and `clearAllAssignments()` in `yii\rbac\ManagerInterface` with `removeAll()`, `removeAllRoles()`, `removeAllPermissions()`, `removeAllRules()` and `removeAllAssignments()` (qiangxue) - Chg: Replaced `clearAll()` and `clearAllAssignments()` in `yii\rbac\ManagerInterface` with `removeAll()`, `removeAllRoles()`, `removeAllPermissions()`, `removeAllRules()` and `removeAllAssignments()` (qiangxue)
......
...@@ -161,3 +161,14 @@ new ones save the following code as `convert.php` that should be placed in the s ...@@ -161,3 +161,14 @@ new ones save the following code as `convert.php` that should be placed in the s
// ... // ...
]; ];
``` ```
* If you are using query caching, you should modify your relevant code as follows, as `beginCache()` and `endCache()` are
replaced by `cache()`:
```php
$db->cache(function ($db) {
// ... SQL queries that need to use query caching
}, $duration, $dependency);
```
...@@ -146,7 +146,7 @@ interface ActiveRecordInterface ...@@ -146,7 +146,7 @@ interface ActiveRecordInterface
* // SELECT FROM customer WHERE age>30 * // SELECT FROM customer WHERE age>30
* $customers = Customer::find()->where('age>30')->all(); * $customers = Customer::find()->where('age>30')->all();
* *
* @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. * @return static|ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
*/ */
public static function find(); public static function find();
......
...@@ -204,30 +204,22 @@ class Connection extends Component ...@@ -204,30 +204,22 @@ class Connection extends Component
* @var boolean whether to enable query caching. * @var boolean whether to enable query caching.
* Note that in order to enable query caching, a valid cache component as specified * Note that in order to enable query caching, a valid cache component as specified
* by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true. * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
* * Also, only the results of the queries enclosed within [[cache()]] will be cached.
* Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
* and off query caching on the fly.
* @see queryCacheDuration
* @see queryCache * @see queryCache
* @see queryCacheDependency * @see cache()
* @see beginCache() * @see noCache()
* @see endCache()
*/ */
public $enableQueryCache = false; public $enableQueryCache = true;
/** /**
* @var integer number of seconds that query results can remain valid in cache. * @var integer the default number of seconds that query results can remain valid in cache.
* Defaults to 3600, meaning 3600 seconds, or one hour.
* Use 0 to indicate that the cached data will never expire. * Use 0 to indicate that the cached data will never expire.
* Defaults to 3600, meaning 3600 seconds, or one hour. Use 0 to indicate that the cached data will never expire.
* The value of this property will be used when [[cache()]] is called without a cache duration.
* @see enableQueryCache * @see enableQueryCache
* @see cache()
*/ */
public $queryCacheDuration = 3600; public $queryCacheDuration = 3600;
/** /**
* @var \yii\caching\Dependency the dependency that will be used when saving query results into cache.
* Defaults to null, meaning no dependency.
* @see enableQueryCache
*/
public $queryCacheDependency;
/**
* @var Cache|string the cache object or the ID of the cache application component * @var Cache|string the cache object or the ID of the cache application component
* that is used for query caching. * that is used for query caching.
* @see enableQueryCache * @see enableQueryCache
...@@ -369,6 +361,10 @@ class Connection extends Component ...@@ -369,6 +361,10 @@ class Connection extends Component
* @var Connection the currently active slave connection * @var Connection the currently active slave connection
*/ */
private $_slave = false; private $_slave = false;
/**
* @var array query cache parameters for the [[cache()]] calls
*/
private $_queryCacheInfo = [];
/** /**
...@@ -381,29 +377,104 @@ class Connection extends Component ...@@ -381,29 +377,104 @@ class Connection extends Component
} }
/** /**
* Turns on query caching. * Uses query cache for the queries performed with the callable.
* This method is provided as a shortcut to setting two properties that are related * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
* with query caching: [[queryCacheDuration]] and [[queryCacheDependency]]. * queries performed within the callable will be cached and their results will be fetched from cache if available.
* @param integer $duration the number of seconds that query results may remain valid in cache. * For example,
* If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details. *
* @param \yii\caching\Dependency $dependency the dependency for the cached query result. * ```php
* See [[queryCacheDependency]] for more details. * // The customer will be fetched from cache if available.
* // If not, the query will be made against DB and cached for use next time.
* $customer = $db->cache(function (Connection $db) {
* return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
* });
* ```
*
* Note that query cache is only meaningful for queries that return results. For queries performed with
* [[Command::execute()]], query cache will not be used.
*
* @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
* The signature of the callable is `function (Connection $db)`.
* @param integer $duration the number of seconds that query results can remain valid in the cache. If this is
* not set, the value of [[queryCacheDuration]] will be used instead.
* Use 0 to indicate that the cached data will never expire.
* @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
* @return mixed the return result of the callable
* @throws \Exception if there is any exception during query
* @see enableQueryCache
* @see queryCache
* @see noCache()
*/
public function cache(callable $callable, $duration = null, $dependency = null)
{
$this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
try {
$result = call_user_func($callable, $this);
array_pop($this->_queryCacheInfo);
return $result;
} catch (\Exception $e) {
array_pop($this->_queryCacheInfo);
throw $e;
}
}
/**
* Disables query cache temporarily.
* Queries performed within the callable will not use query cache at all. For example,
*
* ```php
* $db->cache(function (Connection $db) {
*
* // ... queries that use query cache ...
*
* return $db->noCache(function (Connection $db) {
* // this query will not use query cache
* return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
* });
* });
* ```
*
* @param callable $callable a PHP callable that contains DB queries which should not use query cache.
* The signature of the callable is `function (Connection $db)`.
* @return mixed the return result of the callable
* @throws \Exception if there is any exception during query
* @see enableQueryCache
* @see queryCache
* @see cache()
*/ */
public function beginCache($duration = null, $dependency = null) public function noCache(callable $callable)
{ {
$this->enableQueryCache = true; $this->_queryCacheInfo[] = false;
if ($duration !== null) { try {
$this->queryCacheDuration = $duration; $result = call_user_func($callable, $this);
array_pop($this->_queryCacheInfo);
return $result;
} catch (\Exception $e) {
array_pop($this->_queryCacheInfo);
throw $e;
} }
$this->queryCacheDependency = $dependency;
} }
/** /**
* Turns off query caching. * Returns the current query cache information.
* This method is used internally by [[Command]].
* @return array the current query cache information, or null if query cache is not enabled.
* @internal
*/ */
public function endCache() public function getQueryCacheInfo()
{ {
$this->enableQueryCache = false; $info = end($this->_queryCacheInfo);
if ($this->enableQueryCache) {
if (is_string($this->queryCache) && Yii::$app) {
$cache = Yii::$app->get($this->queryCache, false);
} else {
$cache = $this->queryCache;
}
if ($cache instanceof Cache) {
return is_array($info) ? [$cache, $info[0], $info[1]] : [$cache, null, null];
}
}
return null;
} }
/** /**
......
...@@ -61,4 +61,23 @@ class PDO extends \PDO ...@@ -61,4 +61,23 @@ class PDO extends \PDO
return true; return true;
} }
/**
* Retrieve a database connection attribute.
* It is necessary to override PDO's method as some MSSQL PDO driver (e.g. dblib) does not
* support getting attributes
*/
public function getAttribute($attribute)
{
try {
return parent::getAttribute($attribute);
} catch (\PDOException $e) {
switch ($attribute) {
case PDO::ATTR_SERVER_VERSION:
return $this->query("SELECT SERVERPROPERTY('productversion')")->fetchColumn();
default:
throw $e;
}
}
}
} }
...@@ -17,6 +17,8 @@ use yii\base\InvalidParamException; ...@@ -17,6 +17,8 @@ use yii\base\InvalidParamException;
*/ */
class QueryBuilder extends \yii\db\QueryBuilder class QueryBuilder extends \yii\db\QueryBuilder
{ {
protected $_oldMssql;
/** /**
* @var array mapping from abstract column types (keys) to physical column types (values). * @var array mapping from abstract column types (keys) to physical column types (values).
*/ */
...@@ -189,8 +191,7 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -189,8 +191,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
$originalOrdering = $this->buildOrderBy($query->orderBy); $originalOrdering = $this->buildOrderBy($query->orderBy);
if ($query->select) { if ($query->select) {
$select = implode(', ', $query->select); $select = implode(', ', $query->select);
} } else {
else {
$select = $query->select = '*'; $select = $query->select = '*';
} }
if ($select === '*') { if ($select === '*') {
...@@ -238,8 +239,11 @@ class QueryBuilder extends \yii\db\QueryBuilder ...@@ -238,8 +239,11 @@ class QueryBuilder extends \yii\db\QueryBuilder
*/ */
protected function isOldMssql() protected function isOldMssql()
{ {
if ($this->_oldMssql === null) {
$pdo = $this->db->getSlavePdo(); $pdo = $this->db->getSlavePdo();
$version = preg_split("/\./", $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); $version = preg_split("/\./", $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION));
return $version[0] < 11; $this->_oldMssql = $version[0] < 11;
}
return $this->_oldMssql;
} }
} }
...@@ -79,7 +79,7 @@ class Cors extends ActionFilter ...@@ -79,7 +79,7 @@ class Cors extends ActionFilter
'Origin' => ['*'], 'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'], 'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => true, 'Access-Control-Allow-Credentials' => null,
'Access-Control-Max-Age' => 86400, 'Access-Control-Max-Age' => 86400,
]; ];
...@@ -151,14 +151,19 @@ class Cors extends ActionFilter ...@@ -151,14 +151,19 @@ class Cors extends ActionFilter
} }
} }
$this->prepareAllowHeaders('Method', $requestHeaders, $responseHeaders);
$this->prepareAllowHeaders('Headers', $requestHeaders, $responseHeaders); $this->prepareAllowHeaders('Headers', $requestHeaders, $responseHeaders);
if (isset($requestHeaders['Access-Control-Request-Method'])) {
$responseHeaders['Access-Control-Allow-Methods'] = implode(', ', $this->cors['Access-Control-Request-Method']);
}
if ($this->cors['Access-Control-Allow-Credentials'] === true) { if ($this->cors['Access-Control-Allow-Credentials'] === true) {
$responseHeaders['Access-Control-Allow-Credentials'] = 'true'; $responseHeaders['Access-Control-Allow-Credentials'] = 'true';
} elseif ($this->cors['Access-Control-Allow-Credentials'] === false) { } elseif ($this->cors['Access-Control-Allow-Credentials'] === false) {
$responseHeaders['Access-Control-Allow-Credentials'] = 'false'; $responseHeaders['Access-Control-Allow-Credentials'] = 'false';
} }
if (($_SERVER['REQUEST_METHOD'] === 'OPTIONS') && ($this->cors['Access-Control-Max-Age'] !== null)) { if (($_SERVER['REQUEST_METHOD'] === 'OPTIONS') && ($this->cors['Access-Control-Max-Age'] !== null)) {
$responseHeaders['Access-Control-Max-Age'] = $this->cors['Access-Control-Max-Age']; $responseHeaders['Access-Control-Max-Age'] = $this->cors['Access-Control-Max-Age'];
} }
...@@ -178,20 +183,12 @@ class Cors extends ActionFilter ...@@ -178,20 +183,12 @@ class Cors extends ActionFilter
$responseHeaderField = 'Access-Control-Allow-'.$type; $responseHeaderField = 'Access-Control-Allow-'.$type;
if (isset($requestHeaders[$requestHeaderField])) { if (isset($requestHeaders[$requestHeaderField])) {
if (in_array('*', $this->cors[$requestHeaderField])) { if (in_array('*', $this->cors[$requestHeaderField])) {
if ($type === 'Method') {
$responseHeaders[$responseHeaderField] = strtoupper($requestHeaders[$requestHeaderField]);
} elseif ($type === 'Headers') {
$responseHeaders[$responseHeaderField] = $this->headerize($requestHeaders[$requestHeaderField]); $responseHeaders[$responseHeaderField] = $this->headerize($requestHeaders[$requestHeaderField]);
}
} else { } else {
$requestedData = preg_split("/[\s,]+/", $requestHeaders[$requestHeaderField], -1, PREG_SPLIT_NO_EMPTY); $requestedData = preg_split("/[\s,]+/", $requestHeaders[$requestHeaderField], -1, PREG_SPLIT_NO_EMPTY);
$acceptedData = []; $acceptedData = [];
foreach ($requestedData as $req) { foreach ($requestedData as $req) {
if ($type === 'Method') {
$req = strtoupper($req);
} elseif ($type === 'Headers') {
$req = $this->headerize($req); $req = $this->headerize($req);
}
if (in_array($req, $this->cors[$requestHeaderField])) { if (in_array($req, $this->cors[$requestHeaderField])) {
$acceptedData[] = $req; $acceptedData[] = $req;
} }
......
...@@ -618,7 +618,7 @@ class BaseHtml ...@@ -618,7 +618,7 @@ class BaseHtml
* - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
* in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks.
* When this option is specified, the radio button will be enclosed by a label tag. * When this option is specified, the radio button will be enclosed by a label tag.
* - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. * - labelOptions: array, the HTML attributes for the label tag. Do not set this option unless you set the "label" option.
* - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified. * - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified.
* If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered * If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered
* around the the radio button. * around the the radio button.
...@@ -668,7 +668,7 @@ class BaseHtml ...@@ -668,7 +668,7 @@ class BaseHtml
* - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
* in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks.
* When this option is specified, the checkbox will be enclosed by a label tag. * When this option is specified, the checkbox will be enclosed by a label tag.
* - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. * - labelOptions: array, the HTML attributes for the label tag. Do not set this option unless you set the "label" option.
* - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified. * - container: array|boolean, the HTML attributes for the container tag. This is only used when the "label" option is specified.
* If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered * If it is false, no container will be rendered. If it is an array or not, a "div" container will be rendered
* around the the radio button. * around the the radio button.
......
...@@ -595,7 +595,7 @@ class DbManager extends BaseManager ...@@ -595,7 +595,7 @@ class DbManager extends BaseManager
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function assign($role, $userId, $rule = null, $data = null) public function assign($role, $userId, $rule = null)
{ {
$assignment = new Assignment([ $assignment = new Assignment([
'userId' => $userId, 'userId' => $userId,
......
...@@ -167,11 +167,10 @@ interface ManagerInterface ...@@ -167,11 +167,10 @@ interface ManagerInterface
* @param string|integer $userId the user ID (see [[\yii\web\User::id]]) * @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @param Rule $rule the rule to be associated with this assignment. If not null, the rule * @param Rule $rule the rule to be associated with this assignment. If not null, the rule
* will be executed when [[allow()]] is called to check the user permission. * will be executed when [[allow()]] is called to check the user permission.
* @param mixed $data additional data associated with this assignment.
* @return Assignment the role assignment information. * @return Assignment the role assignment information.
* @throws \Exception if the role has already been assigned to the user * @throws \Exception if the role has already been assigned to the user
*/ */
public function assign($role, $userId, $rule = null, $data = null); public function assign($role, $userId, $rule = null);
/** /**
* Revokes a role from a user. * Revokes a role from a user.
......
...@@ -223,7 +223,7 @@ class PhpManager extends BaseManager ...@@ -223,7 +223,7 @@ class PhpManager extends BaseManager
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function assign($role, $userId, $ruleName = null, $data = null) public function assign($role, $userId, $ruleName = null)
{ {
if (!isset($this->items[$role->name])) { if (!isset($this->items[$role->name])) {
throw new InvalidParamException("Unknown role '{$role->name}'."); throw new InvalidParamException("Unknown role '{$role->name}'.");
......
...@@ -14,6 +14,8 @@ use yii\helpers\FileHelper; ...@@ -14,6 +14,8 @@ use yii\helpers\FileHelper;
/** /**
* FileValidator verifies if an attribute is receiving a valid uploaded file. * FileValidator verifies if an attribute is receiving a valid uploaded file.
* *
* Note that you should enable `fileinfo` PHP extension.
*
* @property integer $sizeLimit The size limit for uploaded files. This property is read-only. * @property integer $sizeLimit The size limit for uploaded files. This property is read-only.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
...@@ -213,7 +215,7 @@ class FileValidator extends Validator ...@@ -213,7 +215,7 @@ class FileValidator extends Validator
return [$this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]]; return [$this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]];
} elseif (!empty($this->extensions) && !$this->validateExtension($file)) { } elseif (!empty($this->extensions) && !$this->validateExtension($file)) {
return [$this->wrongExtension, ['file' => $file->name, 'extensions' => implode(', ', $this->extensions)]]; return [$this->wrongExtension, ['file' => $file->name, 'extensions' => implode(', ', $this->extensions)]];
} elseif (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($file->tempName), $this->mimeTypes, true)) { } elseif (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($file->tempName), $this->mimeTypes, false)) {
return [$this->wrongMimeType, ['file' => $file->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]]; return [$this->wrongMimeType, ['file' => $file->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
} else { } else {
return null; return null;
...@@ -268,7 +270,7 @@ class FileValidator extends Validator ...@@ -268,7 +270,7 @@ class FileValidator extends Validator
*/ */
public function isEmpty($value, $trim = false) public function isEmpty($value, $trim = false)
{ {
$value = is_array($value) && !empty($value) ? $value[0] : $value; $value = is_array($value) ? reset($value) : $value;
return !($value instanceof UploadedFile) || $value->error == UPLOAD_ERR_NO_FILE; return !($value instanceof UploadedFile) || $value->error == UPLOAD_ERR_NO_FILE;
} }
...@@ -306,7 +308,7 @@ class FileValidator extends Validator ...@@ -306,7 +308,7 @@ class FileValidator extends Validator
if ($this->checkExtensionByMimeType) { if ($this->checkExtensionByMimeType) {
$mimeType = FileHelper::getMimeType($file->tempName); $mimeType = FileHelper::getMimeType($file->tempName, null, false);
if ($mimeType === null) { if ($mimeType === null) {
return false; return false;
} }
......
...@@ -101,6 +101,13 @@ index yii2_test_rt_index ...@@ -101,6 +101,13 @@ index yii2_test_rt_index
} }
index yii2_test_distributed
{
type = distributed
local = yii2_test_article_index
}
indexer indexer
{ {
mem_limit = 32M mem_limit = 32M
......
...@@ -324,4 +324,27 @@ class QueryTest extends SphinxTestCase ...@@ -324,4 +324,27 @@ class QueryTest extends SphinxTestCase
->all($connection); ->all($connection);
$this->assertNotEmpty($rows); $this->assertNotEmpty($rows);
} }
/**
* @depends testRun
*
* @see https://github.com/yiisoft/yii2/issues/4375
*/
public function testRunOnDistributedIndex()
{
$connection = $this->getConnection();
$query = new Query;
$rows = $query->from('yii2_test_distributed')
->match('about')
->options([
'cutoff' => 50,
'field_weights' => [
'title' => 10,
'content' => 3,
],
])
->all($connection);
$this->assertNotEmpty($rows);
}
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
namespace yiiunit\framework\db; namespace yiiunit\framework\db;
use yii\caching\FileCache;
use yii\db\Connection;
use yii\db\DataReader; use yii\db\DataReader;
/** /**
...@@ -240,6 +242,7 @@ class CommandTest extends DatabaseTestCase ...@@ -240,6 +242,7 @@ class CommandTest extends DatabaseTestCase
$this->assertEquals(2, $command->execute()); $this->assertEquals(2, $command->execute());
} }
/*
public function testInsert() public function testInsert()
{ {
} }
...@@ -299,6 +302,7 @@ class CommandTest extends DatabaseTestCase ...@@ -299,6 +302,7 @@ class CommandTest extends DatabaseTestCase
public function testDropIndex() public function testDropIndex()
{ {
} }
*/
public function testIntegrityViolation() public function testIntegrityViolation()
{ {
...@@ -311,4 +315,49 @@ class CommandTest extends DatabaseTestCase ...@@ -311,4 +315,49 @@ class CommandTest extends DatabaseTestCase
$command->execute(); $command->execute();
$command->execute(); $command->execute();
} }
public function testQueryCache()
{
$db = $this->getConnection();
$db->enableQueryCache = true;
$db->queryCache = new FileCache(['cachePath' => '@yiiunit/runtime/cache']);
$command = $db->createCommand('SELECT name FROM customer WHERE id=:id');
$this->assertEquals('user1', $command->bindValue(':id', 1)->queryScalar());
$update = $db->createCommand('UPDATE customer SET name=:name WHERE id=:id');
$update->bindValues([':id' => 1, ':name' => 'user11'])->execute();
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$db->cache(function (Connection $db) use ($command, $update) {
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
$update->bindValues([':id' => 2, ':name' => 'user22'])->execute();
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
$db->noCache(function () use ($command) {
$this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
});
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
}, 10);
$db->enableQueryCache = false;
$db->cache(function ($db) use ($command, $update) {
$this->assertEquals('user22', $command->bindValue(':id', 2)->queryScalar());
$update->bindValues([':id' => 2, ':name' => 'user2'])->execute();
$this->assertEquals('user2', $command->bindValue(':id', 2)->queryScalar());
}, 10);
$db->enableQueryCache = true;
$command = $db->createCommand('SELECT name FROM customer WHERE id=:id')->cache();
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$update->bindValues([':id' => 1, ':name' => 'user1'])->execute();
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
$command = $db->createCommand('SELECT name FROM customer WHERE id=:id');
$db->cache(function (Connection $db) use ($command, $update) {
$this->assertEquals('user11', $command->bindValue(':id', 1)->queryScalar());
$this->assertEquals('user1', $command->noCache()->bindValue(':id', 1)->queryScalar());
}, 10);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment