Browse Source

Merge branch 'master' into 2.1

# Conflicts:
#	docs/guide/input-validation.md
#	framework/caching/DbDependency.php
#	framework/validators/UniqueValidator.php
#	tests/framework/validators/UniqueValidatorTest.php
tags/3.0.0-alpha1
Alexander Makarov 8 years ago
parent
commit
575609f21d
No known key found for this signature in database
GPG Key ID: 3617B79C6A325E4A
  1. 8
      .travis.yml
  2. 2
      docs/guide-es/concept-autoloading.md
  3. 7
      docs/guide-ja/caching-data.md
  4. 2
      docs/guide-ja/concept-autoloading.md
  5. 73
      docs/guide-ja/input-forms.md
  6. 149
      docs/guide-ja/input-validation.md
  7. 2
      docs/guide-ja/intro-upgrade-from-v1.md
  8. 2
      docs/guide-ru/concept-autoloading.md
  9. 2
      docs/guide-ru/concept-components.md
  10. 4
      docs/guide-ru/db-migrations.md
  11. 4
      docs/guide-ru/intro-upgrade-from-v1.md
  12. 6
      docs/guide-ru/start-gii.md
  13. 2
      docs/guide-ru/structure-applications.md
  14. 2
      docs/guide-ru/structure-widgets.md
  15. 2
      docs/guide-uk/concept-autoloading.md
  16. 2
      docs/guide-zh-CN/concept-autoloading.md
  17. 2
      docs/guide/concept-components.md
  18. 261
      docs/guide/input-validation.md
  19. 2
      docs/guide/intro-upgrade-from-v1.md
  20. 4
      docs/guide/intro-yii.md
  21. 2
      docs/guide/start-installation.md
  22. 4
      docs/guide/tutorial-performance-tuning.md
  23. 2
      docs/internals/core-code-style.md
  24. 12
      framework/BaseYii.php
  25. 27
      framework/CHANGELOG.md
  26. 10
      framework/UPGRADE.md
  27. 2
      framework/base/Component.php
  28. 10
      framework/caching/ArrayCache.php
  29. 1
      framework/caching/DbDependency.php
  30. 2
      framework/caching/migrations/schema-mssql.sql
  31. 2
      framework/console/controllers/BaseMigrateController.php
  32. 1
      framework/db/cubrid/Schema.php
  33. 38
      framework/db/mssql/QueryBuilder.php
  34. 4
      framework/db/mssql/Schema.php
  35. 6
      framework/db/mysql/Schema.php
  36. 6
      framework/db/oci/Schema.php
  37. 4
      framework/db/pgsql/Schema.php
  38. 20
      framework/helpers/BaseConsole.php
  39. 5
      framework/helpers/BaseInflector.php
  40. 21
      framework/helpers/BaseStringHelper.php
  41. 7
      framework/i18n/migrations/schema-mssql.sql
  42. 13
      framework/log/migrations/m141106_185632_log_init.php
  43. 3
      framework/log/migrations/schema-mssql.sql
  44. 13
      framework/messages/ru/yii.php
  45. 15
      framework/rbac/migrations/schema-mssql.sql
  46. 3
      framework/rest/IndexAction.php
  47. 32
      framework/validators/EachValidator.php
  48. 61
      framework/validators/ExistValidator.php
  49. 6
      framework/validators/NumberValidator.php
  50. 19
      framework/validators/UniqueValidator.php
  51. 5
      framework/web/DbSession.php
  52. 2
      framework/web/ErrorHandler.php
  53. 2
      framework/web/MultipartFormDataParser.php
  54. 11
      framework/web/UrlRule.php
  55. 3
      framework/web/migrations/schema-mssql.sql
  56. 10
      framework/widgets/ActiveField.php
  57. 6
      framework/widgets/InputWidget.php
  58. 210
      tests/framework/behaviors/BlameableBehaviorTest.php
  59. 54
      tests/framework/caching/DbDependencyTest.php
  60. 6
      tests/framework/caching/DependencyTest.php
  61. 8
      tests/framework/db/SchemaTest.php
  62. 14
      tests/framework/db/pgsql/SchemaTest.php
  63. 13
      tests/framework/db/sqlite/SchemaTest.php
  64. 24
      tests/framework/validators/EachValidatorTest.php
  65. 68
      tests/framework/validators/NumberValidatorTest.php
  66. 9
      tests/framework/validators/UniqueValidatorTest.php
  67. 64
      tests/framework/web/ControllerTest.php
  68. 8
      tests/framework/web/MultipartFormDataParserTest.php
  69. 19
      tests/framework/web/ResponseTest.php
  70. 59
      tests/framework/web/UrlRuleTest.php
  71. 39
      tests/framework/widgets/ActiveFieldTest.php

8
.travis.yml

@ -8,6 +8,12 @@ dist: trusty
# faster builds on new travis setup not using sudo
sudo: false
# build only on master branches
branches:
only:
- master
- 2.1
#
# Test Matrix
@ -127,7 +133,7 @@ before_script:
- psql --version
# initialize databases
- mysql -e 'CREATE DATABASE `yiitest`;';
- travis_retry mysql -e 'CREATE DATABASE `yiitest`;';
- mysql -e "CREATE USER 'travis'@'localhost' IDENTIFIED WITH mysql_native_password;";
- mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'localhost' WITH GRANT OPTION;";
- psql -U postgres -c 'CREATE DATABASE yiitest;';

2
docs/guide-es/concept-autoloading.md

@ -3,7 +3,7 @@ Autocarga de clases
Yii depende del [mecanismo de autocarga de clases](http://www.php.net/manual/es/language.oop5.autoload.php) para localizar
e incluir los archivos de las clases requiridas. Proporciona un cargador de clases de alto rendimiento que cumple con el
[estandard PSR-4](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md).
[estandard PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md).
El cargador se instala cuando incluyes el archivo `Yii.php`.
> Note: Para simplificar la descripción, en esta sección sólo hablaremos de la carga automática de clases. Sin embargo,

7
docs/guide-ja/caching-data.md

@ -159,7 +159,7 @@ $value2 = $cache['var2']; // $value2 = $cache->get('var2'); と同等
```php
[
__CLASS__, // クラス名
__CLASS__, // スキーマクラス名
$this->db->dsn, // データベース接続のデータソース名
$this->db->username, // データベース接続のログインユーザ
$name, // テーブル名
@ -168,6 +168,11 @@ $value2 = $cache['var2']; // $value2 = $cache->get('var2'); と同等
見ての通り、キーは一意にデータベースのテーブルを指定するために必要なすべての情報を含んでいます。
> Note: [[yii\caching\Cache::multiSet()|multiSet()]] または [[yii\caching\Cache::multiAdd()|multiAdd()]] によってキャッシュに保存される値が持つことが出来るのは、
文字列または整数のキーだけです。それらより複雑なキーを設定する必要がある場合は、
[[yii\caching\Cache::set()|set()]] または [[yii\caching\Cache::add()|add()]] によって、値を個別に保存してください。
同じキャッシュストレージが異なるアプリケーションによって使用されているときは、キャッシュのキーの競合を避けるために、各アプリケーションではユニークなキーの接頭辞を指定する必要があります。これは [[yii\caching\Cache::keyPrefix]] プロパティを設定することでできます。例えば、アプリケーションのコンフィギュレーションで以下のように書くことができます:
```php

2
docs/guide-ja/concept-autoloading.md

@ -2,7 +2,7 @@
=================
Yiiは、必要となるすべてのクラスファイルを特定してインクルードするにあたり、 [クラスのオートローディングメカニズム](http://www.php.net/manual/ja/language.oop5.autoload.php)
を頼りにします。Yii は、[PSR-4 標準](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md) に準拠した、高性能なクラスのオートローダーを提供しています。
を頼りにします。Yii は、[PSR-4 標準](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) に準拠した、高性能なクラスのオートローダーを提供しています。
このオートローダーは、あなたが `Yii.php` ファイルをインクルードするときにインストールされます。
> Note: 説明を簡単にするため、このセクションではクラスのオートローディングについてのみお話しします。しかし、

73
docs/guide-ja/input-forms.md

@ -1,6 +1,8 @@
フォームを作成する
==================
アクティブレコードに基づくフォーム : ActiveForm
-----------------------------------------------
Yii においてフォームを使用するときは、主として [[yii\widgets\ActiveForm]] による方法を使います。
フォームがモデルに基づくものである場合はこの方法を選ぶべきです。
これに加えて、[[yii\helpers\Html]] にはいくつかの有用なメソッドがあり、どんなフォームでも、ボタンやヘルプテキストを追加するのには、通常、それらのメソッドを使います。
@ -50,12 +52,14 @@ $form = ActiveForm::begin([
<?php ActiveForm::end() ?>
```
### `begin()``end()` で囲む <span id="wrapping-with-begin-and-end"></span>
上記のコードでは、[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] がフォームのインスタンスを作成するとともに、フォームの開始をマークしています。
[[yii\widgets\ActiveForm::begin()|ActiveForm::begin()]] と [[yii\widgets\ActiveForm::end()|ActiveForm::end()]] の間に置かれた全てのコンテントが HTML の `<form>` タグによって囲まれます。
どのウィジェットでも同じですが、ウィジェットをどのように構成すべきかに関するオプションを指定するために、`begin` メソッドに配列を渡すことが出来ます。
この例では、追加の CSS クラスと要素を特定するための ID が渡されて、`<form>` の開始タグに適用されています。
利用できるオプションの全ては [[yii\widgets\ActiveForm]] の API ドキュメントに記されていますので参照してください。
### ActiveField <span id="activefield"></span>.
フォームの中では、フォームの要素を作成するために、ActiveForm ウィジェットの [[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドが呼ばれています。
このメソッドは、フォームの要素だけでなく、そのラベルも作成し、適用できる JavaScript の検証メソッドがあれば、それも追加します。
[[yii\widgets\ActiveForm::field()|ActiveForm::field()]] メソッドは、[[yii\widgets\ActiveField]] のインスタンスを返します。
@ -112,26 +116,75 @@ echo $form->field($model, 'items[]')->checkboxList(['a' => 'Item A', 'b' => 'Ite
> }
> ```
ドロップダウンリストを作る <span id="creating-activeform-dropdownlist"></span>
リストを作る <span id="creating-activeform-lists"></span>
--------------------------
ActiveForm の [dropDownList()](http://www.yiiframework.com/doc-2.0/yii-widgets-activefield.html#dropDownList()-detail)
メソッドを使ってドロップダウンリストを作ることが出来ます。
三種類のリストがあります:
* ドロップダウンリスト
* ラジオリスト
* チェックボックスリスト
リストを作るためには、項目の配列を準備しなければなりません。これは、手作業でやることも出来ます。
```php
use app\models\ProductCategory;
$items = [
1 => '項目 1',
2 => '項目 2'
]
```
または、DB から取得することも出来ます。
```php
$items = Category::find()
->select(['id', 'label'])
->indexBy('id')
->column();
```
このような `$items` が、いろんなリストウィジェットによって処理されるべきものとなります。
フォームのフィールドの値(および現在アクティブな項目)は、`$model` の属性の現在の値に従って自動的に設定されます。
/* @var $this yii\web\View */
#### ドロップダウンリストを作る <span id="creating-activeform-dropdownlist"></span>
ActiveField の [[\yii\widgets\ActiveField::dropDownList()]] メソッドを使って、ドロップダウンリストを作ることが出来ます。
```php
/* @var $form yii\widgets\ActiveForm */
/* @var $model app\models\Product */
echo $form->field($model, 'product_category')->dropdownList(
ProductCategory::find()->select(['category_name', 'id'])->indexBy('id')->column(),
['prompt'=>'カテゴリを選択してください']
echo $form->field($model, 'category')->dropdownList([
1 => '項目 1',
2 => '項目 2'
],
['prompt'=>'カテゴリーを選択してください']
);
```
モデルのフィールドの値は、前もって自動的に選択されます。
#### ラジオリストを作る <span id="creating-activeform-radioList"></span>
ActiveField の [[\yii\widgets\ActiveField::radioList()]] メソッドを使ってラジオリストを作ることが出来ます。
```php
/* @var $form yii\widgets\ActiveForm */
echo $form->field($model, 'category')->radioList([
1 => 'ラジオ 1',
2 => 'ラジオ 2'
]);
```
#### チェックボックスリストを作る <span id="creating-activeform-checkboxList"></span>
ActiveField の [[\yii\widgets\ActiveField::checkboxList()]] メソッドを使ってチェックボックスリストを作ることが出来ます。
```php
/* @var $form yii\widgets\ActiveForm */
echo $form->field($model, 'category')->checkboxList([
1 => 'チェックボックス 1',
2 => 'チェックボックス 2'
]);
```
Pjax を使う <span id="working-with-pjax"></span>

149
docs/guide-ja/input-validation.md

@ -311,8 +311,10 @@ Yii のリリースに含まれている [コアバリデータ](tutorial-core-v
/**
* @param string $attribute 現在検証されている属性
* @param mixed $params 規則に与えられる "params" の値
* @param \yii\validators\InlineValidator 関係する InlineValidator のインスタンス。
* このパラメータは、バージョン 2.0.11 以降で利用可能。
*/
function ($attribute, $params)
function ($attribute, $params, $validator)
```
属性が検証に失敗した場合は、メソッド/関数 は [[yii\base\Model::addError()]] を呼んでエラーメッセージをモデルに保存し、後で読み出してエンドユーザに表示することが出来るようにしなければなりません。
@ -334,7 +336,7 @@ class MyForm extends Model
['country', 'validateCountry'],
// 無名関数として定義されるインラインバリデータ
['token', function ($attribute, $params) {
['token', function ($attribute, $params, $validator) {
if (!ctype_alnum($this->$attribute)) {
$this->addError($attribute, 'トークンは英数字で構成しなければなりません。');
}
@ -342,7 +344,7 @@ class MyForm extends Model
];
}
public function validateCountry($attribute, $params)
public function validateCountry($attribute, $params, $validator)
{
if (!in_array($this->$attribute, ['USA', 'Web'])) {
$this->addError($attribute, '国は "USA" または "Web" でなければなりません。');
@ -351,6 +353,14 @@ class MyForm extends Model
}
```
> Note: バージョン 2.0.11 以降では、代わりに、[[yii\validators\InlineValidator::addError()]] を使ってエラーメッセージを追加することが出来ます。
> そうすれば、エラーメッセージはそのまま [[yii\i18n\I18N::format()]] を使ってフォーマットされます。
> 属性のラベルと値を参照するためには、それぞれ、`{attribute}` と `{value}` を使ってください(手作業で取得する必要はありません)。
>
> ```php
> $validator->addError($this, $attribute, 'The value "{value}" is not acceptable for {attribute}.');
> ```
> Note: デフォルトでは、インラインバリデータは、関連付けられている属性が空の入力値を受け取ったり、既に何らかの検証規則に失敗したりしている場合には、適用されません。
> 規則が常に適用されることを保証したい場合は、規則の宣言において [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] および/または [[yii\validators\Validator::skipOnError|skipOnError]] のプロパティを false に設定することが出来ます。
> 例えば、
@ -418,6 +428,121 @@ class EntryForm extends Model
}
```
## 複数の属性の検証 <span id="multiple-attributes-validation"></span>
時として、バリデータが複数の属性に関係する場合があります。次のようなフォームを考えてみてください。
```php
class MigrationForm extends \yii\base\Model
{
/**
* 成人一人のための最低限の生活費
*/
const MIN_ADULT_FUNDS = 3000;
/**
* こども一人のための最低限の生活費
*/
const MIN_CHILD_FUNDS = 1500;
public $personalSalary; // 給与
public $spouseSalary; // 配偶者の給与
public $childrenCount; // こどもの数
public $description;
public function rules()
{
return [
[['personalSalary', 'description'], 'required'],
[['personalSalary', 'spouseSalary'], 'integer', 'min' => self::MIN_ADULT_FUNDS],
['childrenCount', 'integer', 'min' => 0, 'max' => 5],
[['spouseSalary', 'childrenCount'], 'default', 'value' => 0],
['description', 'string'],
];
}
}
```
### バリデータを作成する <span id="multiple-attributes-validator"></span>
家族の収入が子ども達のために十分であるかどうかをチェックする必要があるとしましょう。
そのためには、`childrenCount` が 1 以上である場合にのみ実行される `validateChildrenFunds` というインラインバリデータを作れば良いわけです。
検証されるすべての属性 (`['personalSalary', 'spouseSalary', 'childrenCount']`) にこのバリデータをアタッチすることは出来ない、ということに注意してください。
そのようにすると、同じバリデータが属性ごとに (合計で三回) 走ることになりますが、
属性のセット全体に対してこのバリデータを一度だけ走らせれば十分なのです。
これらの属性のどれを使っても構いません (あるいは、もっとも関係が深いと思うものを使ってください)。
```php
['childrenCount', 'validateChildrenFunds', 'when' => function ($model) {
return $model->childrenCount > 0;
}],
```
`validateChildrenFunds` の実装は次のようにすることが出来ます。
```php
public function validateChildrenFunds($attribute, $params)
{
$totalSalary = $this->personalSalary + $this->spouseSalary;
// 配偶者の給与が指定されているときは、成人の最低生活費を倍にする
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('childrenCount', '子どもの数に対して給与が不足しています。');
}
}
```
この検証は属性一つだけに関係するものではないので、`$attribute` のパラメータは無視することが出来ます。
### エラーメッセージを追加する <span id="multiple-attributes-errors"></span>
複数の属性の場合のエラーメッセージの追加は、フォームをどのように設計するかによって異なってきます。
- もっとも関係が深いとあなたが思うフィールドを選んで、その属性にエラーメッセージを追加する。
```php
$this->addError('childrenCount', '子どもの数に対して給与が不足しています。');
```
- 重要な複数の属性、または、すべての属性を選んで、同じエラーメッセージを追加する。
メッセージを独立した変数に格納してから `addError` に渡せば、コードを DRY に保つことが出来ます。
```php
$message = '子どもの数に対して給与が不足しています。';
$this->addError('personalSalary', $message);
$this->addError('wifeSalary', $message);
$this->addError('childrenCount', $message);
```
あるいは、ループを使います。
```php
$attributes = ['personalSalary, 'wifeSalary', 'childrenCount'];
foreach ($attributes as $attribute) {
$this->addError($attribute, '子どもの数に対して給与が不足しています。');
}
```
- (特定の属性に結び付かない) 共通のエラーメッセージを追加する。
その時点では属性の存在はチェックされませんので、存在しない属性の名前、例えば `*` を使ってエラーメッセージを追加することが出来ます。
```php
$this->addError('*', '子どもの数に対して給与が不足しています。');
```
結果として、フォームのフィールドの近くにはこのエラーメッセージは表示されません。
これを表示するためには、ビューにエラーサマリーを含めます。
```php
<?= $form->errorSummary($model) ?>
```
> Note: 複数の属性を一度に検証するバリデータを作成する方法が [community cookbook](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-validator-multiple-attributes.md) で分り易く解説されています。.
## クライアント側での検証 <span id="client-side-validation"></span>
エンドユーザが HTML フォームで値を入力する際には、JavaScript に基づくクライアント側での検証を提供することが望まれます。
@ -485,10 +610,26 @@ class LoginForm extends Model
舞台裏では、[[yii\widgets\ActiveForm]] がモデルで宣言されている検証規則を読んで、クライアント側の検証をサポートするバリデータのために、適切な JavaScript コードを生成します。
ユーザが入力フィールドの値を変更したりフォームを送信したりすると、クライアント側の検証の JavaScript が起動されます。
クライアント側の検証を完全に無効にしたい場合は、[[yii\widgets\ActiveForm::enableClientValidation]] プロパティを false に設定することが出来ます。
クライアント側の検証を完全に無効にしたい場合は、[[yii\widgets\ActiveForm::enableClientValidation]] プロパティを `false` に設定することが出来ます。
また、個々の入力フィールドごとにクライアント側の検証を無効にしたい場合には、入力フィールドの [[yii\widgets\ActiveField::enableClientValidation]] プロパティを false に設定することが出来ます。
`eanbleClientValidation` が入力フィールドのレベルとフォームのレベルの両方で構成されている場合は前者が優先されます。
> Info: バージョン 2.0.11 以降、[[yii\validators\Validator]] を拡張する全てのバリデータは、
> クライアント側のオプションを独立したメソッド - [[yii\validators\Validator::getClientOptions()]] から受け取るようになりました。
> これを使うと、次のことが可能になります。
>
> - 独自のクライアント側検証を実装しながら、サーバ側検証のオプションとの同期はそのまま残す
> - 特殊な要求に合うように拡張またはカスタマイズする
>
> ```php
> public function getClientOptions($model, $attribute)
> {
> $options = parent::getClientOptions($model, $attribute);
> // ここで $options を修正
>
> return $options;
> }
> ```
### クライアント側の検証を実装する <span id="implementing-client-side-validation"></span>

2
docs/guide-ja/intro-upgrade-from-v1.md

@ -535,5 +535,5 @@ Yii 1.1 と 2.x を一緒に使う
---------------------------
Yii 2.0 と一緒に使いたい Yii 1.1 のレガシーコードを持っている場合は、
[サードパーティのコードを扱](tutorial-yii-integration.md) の節を参照してください。
[Yii 1.1 と 2.0 を一緒に使](tutorial-yii-integration.md#using-both-yii2-yii) の節を参照してください。

2
docs/guide-ru/concept-autoloading.md

@ -3,7 +3,7 @@
Поиск и подключение файлов классов в Yii реализовано при помощи
[автозагрузки классов](http://www.php.net/manual/ru/language.oop5.autoload.php). Фреймворк предоставляет свой быстрый
совместимый с [PSR-4](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)
совместимый с [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md)
автозагрузчик, который устанавливается в момент подключения `Yii.php`.
> Note: Для простоты повествования, в этом разделе мы будем говорить только об автозагрузке классов. Тем не менее,

2
docs/guide-ru/concept-components.md

@ -10,7 +10,7 @@
Как по отдельности, так и вместе, эти возможности делают классы Yii более простыми в настройке и использовании.
Например, пользовательские компоненты, включающие в себя [[yii\jui\DatePicker|виджет выбора даты]], могут быть
использованы в [представлении](structure-view.md) для генерации интерактивных элементов выбора даты:
использованы в [представлении](structure-views.md) для генерации интерактивных элементов выбора даты:
```php
use yii\jui\DatePicker;

4
docs/guide-ru/db-migrations.md

@ -817,13 +817,13 @@ yii migrate/create 'app\\migrations\\createUserTable'
```
> Замечание: миграции заданные через [[yii\console\controllers\MigrateController::migrationPath|migrationPath]] не могут содержать
пространство имен, миграции, объявленные с пространством имен могкт быть применены только используя свойство [[yii\console\controllers\MigrateController::migrationNamespaces]].
пространство имен, миграции, объявленные с пространством имен могут быть применены только используя свойство [[yii\console\controllers\MigrateController::migrationNamespaces]].
### Отдельностоящие Миграции <span id="separated-migrations"></span>
Иногда использование единой истории для всех миграция проекта не желательно. Например: вы можете установить расширение
'blog', которое содержить полностью независимый функционал и содержит собственные миграции, которые не должны затрагивать
'blog', которое содержит полностью независимый функционал и содержит собственные миграции, которые не должны затрагивать
миграции связанные с основной функциональностью проекта.
Если необходимо, чтобы миграции из разных источников были независимы друг от друга, вы можете сконфигурировать

4
docs/guide-ru/intro-upgrade-from-v1.md

@ -18,7 +18,7 @@
Yii 2.0 широко использует [Composer](https://getcomposer.org/), который является основным менеджером зависимостей для PHP.
Установка как фреймворка, так и расширений, осуществляется через Composer. Подробно о установке Yii 2.0 вы можете узнать
из раздела «[Установка Yii](start-installation.md)». О том, как создавать расширения для Yii 2.0 или адаптировать
уже имеющиеся расширения от версии 1.1, вы можете узнать из раздела «[Создание расширений](extend-creating-extensions.md)».
уже имеющиеся расширения от версии 1.1, вы можете узнать из раздела «[Создание расширений](structure-extensions.md#creating-extensions)».
Требования PHP
@ -528,4 +528,4 @@ User и IdentityInterface
----------------------------------
Информация об использовании кода для Yii 1.1 вместе с Yii 2.0 представлена в разделе
«[Одновременное использование Yii 1.1 и 2.0](extend-using-v1-v2.md)».
«[Одновременное использование Yii 1.1 и 2.0](tutorial-yii-integration.md#using-both-yii2-yii1)».

6
docs/guide-ru/start-gii.md

@ -1,7 +1,7 @@
Генерация кода при помощи Gii
========================
В этом разделе мы опишем, как использовать [Gii](tool-gii.md) для автоматической генерации кода,
В этом разделе мы опишем, как использовать [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ru/README.md) для автоматической генерации кода,
реализующего некоторые общие функции вебсайта. Для достижения этой цели всё, что вам нужно, это просто ввести необходимую информацию в соответствии с инструкциями, отображаемыми на веб-страницах Gii.
В этом руководстве вы узнаете:
@ -15,7 +15,7 @@
Запускаем Gii <span id="starting-gii"></span>
------------
[Gii](tool-gii.md) представлен в Yii как [модуль](structure-modules.md). Вы можете активировать Gii,
[Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ru/README.md) представлен в Yii как [модуль](structure-modules.md). Вы можете активировать Gii,
настроив его в свойстве [[yii\base\Application::modules|modules]]. В зависимости от того, каким образом вы создали приложение, вы можете удостовериться в наличии следующего кода в конфигурационном файле `config/web.php`,
```php
@ -119,7 +119,7 @@ http://hostname/index.php?r=country%2Findex
* Модели: `models/Country.php` и `models/CountrySearch.php`
* Вид: `views/country/*.php`
> Info: Gii разработан как тонконастраиваемый и расширяемый инструмент генерации кода. Используя его с умом, вы можете значительно ускорить скорость разработки приложений. Для более подробной информации, пожалуйста, обратитесь к разделу [Gii](tool-gii.md).
> Info: Gii разработан как тонконастраиваемый и расширяемый инструмент генерации кода. Используя его с умом, вы можете значительно ускорить скорость разработки приложений. Для более подробной информации, пожалуйста, обратитесь к разделу [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide-ru/README.md).
Заключение <span id="summary"></span>

2
docs/guide-ru/structure-applications.md

@ -190,7 +190,7 @@ if (YII_ENV_DEV) {
#### [[yii\base\Application::components|components]] <span id="components"></span>
Данное свойство является наиболее важным. Оно позволяет вам зарегистрировать список именованных компонентов, называемых
[компоненты приложения](#structure-application-components.md), которые Вы можете использовать в других местах.
[компоненты приложения](structure-application-components.md), которые Вы можете использовать в других местах.
Например,
```php

2
docs/guide-ru/structure-widgets.md

@ -201,7 +201,7 @@ public function run()
Разрабатываемые виджеты должны быть самодостаточными. Это означает, что для их использования должно быть
достаточно всего лишь добавить виджет в представление. Добиться этого бывает затруднительно в том случае,
когда для его функционирования требуются внешние ресурсы, такие как CSS, JavaScript, изображения и т.д.
К счастью, Yii предоставляет поддержку механизма для работы с ресурсами [asset bundles](structure-asset-bundles.md),
К счастью, Yii предоставляет поддержку механизма для работы с ресурсами [asset bundles](structure-assets.md),
который может быть успешно использован для решения данной проблемы.
В случае, когда виджет не содержит логики, а содержит только код, отвечающий за вывод разметки, он мало

2
docs/guide-uk/concept-autoloading.md

@ -4,7 +4,7 @@
Пошук і підключення файлів класів в Yii реалізовано за допомогою
[автозавантаження класів](http://www.php.net/manual/ru/language.oop5.autoload.php).
Фреймворк надає власний швидкісний автозавантажувач, що сумісний з
[PSR-4](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md),
[PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md),
який встановлюється в момент підключення файлу `Yii.php`.
> Note: Для простоти опису, в цьому розділі ми будемо говорити тільки про автозавантаження класів.

2
docs/guide-zh-CN/concept-autoloading.md

@ -1,7 +1,7 @@
类自动加载(Autoloading)
=================
Yii 依靠[类自动加载机制](http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/proposed/psr-4-autoloader/psr-4-autoloader.md)([中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。
Yii 依靠[类自动加载机制](http://www.php.net/manual/en/language.oop5.autoload.php)来定位和包含所需的类文件。它提供一个高性能且完美支持[PSR-4 标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md)([中文汉化](https://github.com/hfcorriez/fig-standards/blob/zh_CN/%E6%8E%A5%E5%8F%97/PSR-4-autoloader.md))的自动加载器。该自动加载器会在引入框架文件 `Yii.php` 时安装好。
> Note: 为了简化叙述,本篇文档中我们只会提及类的自动加载。不过,要记得文中的描述同样也适用于接口和Trait(特质)的自动加载哦。

2
docs/guide/concept-components.md

@ -9,7 +9,7 @@ or an extended class. The three main features that components provide to other c
* [Behaviors](concept-behaviors.md)
Separately and combined, these features make Yii classes much more customizable and easier to use. For example,
the included [[yii\jui\DatePicker|date picker widget]], a user interface component, can be used in a [view](structure-view.md)
the included [[yii\jui\DatePicker|date picker widget]], a user interface component, can be used in a [view](structure-views.md)
to generate an interactive date picker:
```php

261
docs/guide/input-validation.md

@ -563,266 +563,7 @@ As a result, we will not see error message near form fields. To display it, we c
<?= $form->errorSummary($model) ?>
```
### Custom validator <span id="multiple-attributes-custom-validator"></span>
If passing one attribute is not acceptable for you (for example it can be hard to choose which one is more relevant or
you consider it misleading in rules), the more advanced solution is to implement `CustomValidator` with support for
validating multiple attributes at once.
By default if multiple attributes are used for validation, the loop will be used to apply the same validation to each
of them. Let's use a separate trait and override [[yii\base\Validator:validateAttributes()]]:
```php
<?php
namespace app\components;
trait BatchValidationTrait
{
/**
* @var bool whether to validate multiple attributes at once
*/
public $batch = false;
/**
* Validates the specified object.
* @param \yii\base\Model $model the data model being validated.
* @param array|null $attributes the list of attributes to be validated.
* Note that if an attribute is not associated with the validator, or is is prefixed with `!` char - it will be
* ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated.
*/
public function validateAttributes($model, $attributes = null)
{
if (is_array($attributes)) {
$newAttributes = [];
foreach ($attributes as $attribute) {
if (in_array($attribute, $this->attributes) || in_array('!' . $attribute, $this->attributes)) {
$newAttributes[] = $attribute;
}
}
$attributes = $newAttributes;
} else {
$attributes = [];
foreach ($this->attributes as $attribute) {
$attributes[] = $attribute[0] === '!' ? substr($attribute, 1) : $attribute;
}
}
foreach ($attributes as $attribute) {
$skip = $this->skipOnError && $model->hasErrors($attribute)
|| $this->skipOnEmpty && $this->isEmpty($model->$attribute);
if ($skip) {
// Skip validation if at least one attribute is empty or already has error
// (according skipOnError and skipOnEmpty options must be set to true
return;
}
}
if ($this->batch) {
// Validate all attributes at once
if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
// Pass array with all attributes instead of one attribute
$this->validateAttribute($model, $attributes);
}
} else {
// Validate each attribute separately using the same validation logic
foreach ($attributes as $attribute) {
if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
$this->validateAttribute($model, $attribute);
}
}
}
}
}
```
Then we need to create custom validator and use the created trait:
```php
<?php
namespace app\components;
use yii\validators\Validator;
class CustomValidator extends Validator
{
use BatchValidationTrait;
}
```
To support inline validation as well we can extend default inline validator and also use this trait:
```php
<?php
namespace app\components;
use yii\validators\InlineValidator;
class CustomInlineValidator extends InlineValidator
{
use BatchValidationTrait;
}
```
Couple more changes are needed.
First to use our `CustomInlineValidator` instead of default `InlineValidator` we need to override
[[\yii\validators\Validator::createValidator()]] method in `CustomValidator`:
```php
public static function createValidator($type, $model, $attributes, $params = [])
{
$params['attributes'] = $attributes;
if ($type instanceof \Closure || $model->hasMethod($type)) {
// method-based validator
// The following line is changed to use our CustomInlineValidator
$params['class'] = __NAMESPACE__ . '\CustomInlineValidator';
$params['method'] = $type;
} else {
if (isset(static::$builtInValidators[$type])) {
$type = static::$builtInValidators[$type];
}
if (is_array($type)) {
$params = array_merge($type, $params);
} else {
$params['class'] = $type;
}
}
return Yii::createObject($params);
}
```
And finally to support our custom validator in model we can create the trait and override
[[\yii\base\Model::createValidators()]] like this:
```php
<?php
namespace app\components;
use yii\base\InvalidConfigException;
trait CustomValidationTrait
{
/**
* Creates validator objects based on the validation rules specified in [[rules()]].
* Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
* @return ArrayObject validators
* @throws InvalidConfigException if any validation rule configuration is invalid
*/
public function createValidators()
{
$validators = new ArrayObject;
foreach ($this->rules() as $rule) {
if ($rule instanceof Validator) {
$validators->append($rule);
} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
// The following line is changed in order to use our CustomValidator
$validator = CustomValidator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
$validators->append($validator);
} else {
throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
}
}
return $validators;
}
}
```
Now we can implement custom validator by extending from `CustomValidator`:
```php
<?php
namespace app\validators;
use app\components\CustomValidator;
class ChildrenFundsValidator extends CustomValidator
{
public function validateAttribute($model, $attribute)
{
// $attribute here is not a single attribute, it's an array containing all related attributes
$totalSalary = $this->personalSalary + $this->spouseSalary;
// Double the minimal adult funds if spouse salary is specified
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('*', 'Your salary is not enough for children.');
}
}
}
```
Because `$attribute` contains the list of all related attributes, we can use loop in case of adding errors for all
attributes is needed:
```php
foreach ($attribute as $singleAttribute) {
$this->addError($attribute, 'Your salary is not enough for children.');
}
```
Now it's possible to specify all related attributes in according validation rule:
```php
[
['personalSalary', 'spouseSalary', 'childrenCount'],
\app\validators\ChildrenFundsValidator::class,
'batch' => `true`,
'when' => function ($model) {
return $model->childrenCount > 0;
}
],
```
For inline validation the rule will be:
```php
[
['personalSalary', 'spouseSalary', 'childrenCount'],
'validateChildrenFunds',
'batch' => `true`,
'when' => function ($model) {
return $model->childrenCount > 0;
}
],
```
And here is according validation method:
```php
public function validateChildrenFunds($attribute, $params)
{
// $attribute here is not a single attribute, it's an array containing all related attributes
$totalSalary = $this->personalSalary + $this->spouseSalary;
// Double the minimal adult funds if spouse salary is specified
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('childrenCount', 'Your salary is not enough for children.');
}
}
```
The advantages of this approach:
- It better reflects all attributes that participate in validation (the rules become more readable);
- It respects the options [[yii\validators\Validator::skipOnError]] and [[yii\validators\Validator::skipOnEmpty]] for
**each** used attribute (not only for that you decided to choose as more relevant).
If you have problems with implementing client validation, you can:
- combine [[yii\widgets\ActiveForm::enableAjaxValidation|enableClientValidation]] and
[[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] options, so multiple attributes will be validated
with AJAX without page reload;
- implement validation outside of [[yii\validators\Validator::clientValidateAttribute]] because it's designed to work
with single attribute.
> Note: Creating validator which validates multiple attributes at once is well described in the [community cookbook](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-validator-multiple-attributes.md).
## Client-Side Validation <span id="client-side-validation"></span>

2
docs/guide/intro-upgrade-from-v1.md

@ -536,5 +536,5 @@ Using Yii 1.1 and 2.x together
------------------------------
If you have legacy Yii 1.1 code that you want to use together with Yii 2.0, please refer to
the [Using Yii 1.1 and 2.0 Together](tutorial-yii-integration.md) section.
the [Using Yii 1.1 and 2.0 Together](tutorial-yii-integration.md#using-both-yii2-yii1) section.

4
docs/guide/intro-yii.md

@ -50,8 +50,8 @@ This guide is mainly about version 2.0.
Requirements and Prerequisites
------------------------------
Yii 2.0 requires PHP 5.4.0 or above. You can find more detailed requirements for individual features
by running the requirement checker included in every Yii release.
Yii 2.0 requires PHP 5.4.0 or above and runs best with the latest version of PHP 7. You can find more detailed
requirements for individual features by running the requirement checker included in every Yii release.
Using Yii requires basic knowledge of object-oriented programming (OOP), as Yii is a pure OOP-based framework.
Yii 2.0 also makes use of the latest features of PHP, such as [namespaces](http://www.php.net/manual/en/language.namespaces.php)

2
docs/guide/start-installation.md

@ -152,7 +152,7 @@ Yii's requirements. You can check if the minimum requirements are met using one
```
You should configure your PHP installation so that it meets the minimum requirements of Yii. Most importantly, you
should have PHP 5.4 or above. You should also install the [PDO PHP Extension](http://www.php.net/manual/en/pdo.installation.php)
should have PHP 5.4 or above. Ideally latest PHP 7. You should also install the [PDO PHP Extension](http://www.php.net/manual/en/pdo.installation.php)
and a corresponding database driver (such as `pdo_mysql` for MySQL databases), if your application needs a database.

4
docs/guide/tutorial-performance-tuning.md

@ -11,8 +11,8 @@ factors and explain how you can improve your application performance by adjustin
A well configured PHP environment is very important. In order to get maximum performance,
- Use the latest stable PHP version. Major releases of PHP may bring significant performance improvements.
- Enable bytecode caching with [Opcache](http://php.net/opcache) (PHP 5.5 or later) or [APC](http://ru2.php.net/apc)
(PHP 5.4 or earlier). Bytecode caching avoids the time spent in parsing and including PHP scripts for every
- Enable bytecode caching with [Opcache](http://php.net/opcache) (PHP 5.5 or later) or [APC](http://php.net/apc)
(PHP 5.4). Bytecode caching avoids the time spent in parsing and including PHP scripts for every
incoming request.
- [Tune `realpath()` cache](https://github.com/samdark/realpath_cache_tuner).

2
docs/internals/core-code-style.md

@ -153,7 +153,7 @@ class Foo
```php
/**
* Checkes whether the IP is in subnet range
* Checks whether the IP is in subnet range
*
* @param string $ip an IPv4 or IPv6 address
* @param int $cidr the CIDR lendth

12
framework/BaseYii.php

@ -378,7 +378,8 @@ class BaseYii
* Logs a trace message.
* Trace messages are logged mainly for development purpose to see
* the execution work flow of some code.
* @param string $message the message to be logged.
* @param string|array $message the message to be logged. This can be a simple string or a more
* complex data structure, such as array.
* @param string $category the category of the message.
*/
public static function trace($message, $category = 'application')
@ -392,7 +393,8 @@ class BaseYii
* Logs an error message.
* An error message is typically logged when an unrecoverable error occurs
* during the execution of an application.
* @param string $message the message to be logged.
* @param string|array $message the message to be logged. This can be a simple string or a more
* complex data structure, such as array.
* @param string $category the category of the message.
*/
public static function error($message, $category = 'application')
@ -404,7 +406,8 @@ class BaseYii
* Logs a warning message.
* A warning message is typically logged when an error occurs while the execution
* can still continue.
* @param string $message the message to be logged.
* @param string|array $message the message to be logged. This can be a simple string or a more
* complex data structure, such as array.
* @param string $category the category of the message.
*/
public static function warning($message, $category = 'application')
@ -416,7 +419,8 @@ class BaseYii
* Logs an informative message.
* An informative message is typically logged by an application to keep record of
* something important (e.g. an administrator logs in).
* @param string $message the message to be logged.
* @param string|array $message the message to be logged. This can be a simple string or a more
* complex data structure, such as array.
* @param string $category the category of the message.
*/
public static function info($message, $category = 'application')

27
framework/CHANGELOG.md

@ -22,12 +22,12 @@ Yii Framework 2 Change Log
2.0.11 under development
------------------------
- Bug #13277: Fixed invalid parsing of `--` ("End of Options" special argument) in CLI (rugabarbo)
- Bug #4113: Error page stacktrace was generating links to private methods which are not part of the API docs (samdark)
- Bug #7727: Fixed `yii\helpers\StringHelper::truncateHtml()` leaving extra tags (developeruz)
- Bug #9305: Fixed MSSQL `Schema::TYPE_TIMESTAMP` to be 'datetime' instead of 'timestamp', which is just an incremental number (nkovacs)
- Bug #9616: Fixed mysql\Schema::loadColumnSchema to set enumValues attribute correctly if enum definition contains commas (fphammerle)
- Bug #9796: Initialization of not existing `yii\grid\ActionColumn` default buttons (arogachev)
- Bug #10488: Fixed incorrect behavior of `yii\validation\NumberValidator` when used with locales where decimal separator is comma (quantum13, samdark, rob006)
- Bug #11122: Fixed can not use `orderBy` with aggregate functions like `count` (Ni-san)
- Bug #11771: Fixed semantics of `yii\di\ServiceLocator::__isset()` to match the behavior of `__get()` which fixes inconsistent behavior on newer PHP versions (cebe)
- Bug #12213: Fixed `yii\db\ActiveRecord::unlinkAll()` to respect `onCondition()` of the relational query (silverfire)
@ -48,6 +48,7 @@ Yii Framework 2 Change Log
- Bug #12880: Fixed `yii\behaviors\AttributeTypecastBehavior` marks attributes with `null` value as 'dirty' (klimov-paul)
- Bug #12904: Fixed lowercase table name in migrations (zlakomanoff)
- Bug #12939: Hard coded table names for MSSQL in RBAC migration (arogachev)
- Bug #12969: Improved unique ID generation for `yii\widgets\Pjax` widgets (dynasource, samdark, rob006)
- Bug #12974: Fixed incorrect order of migrations history in case `yii\console\controllers\MigrateController::$migrationNamespaces` is in use (evgen-d, klimov-paul)
- 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)
@ -58,19 +59,26 @@ Yii Framework 2 Change Log
- Bug #13159: Fixed `destroy` method in `yii.captcha.js` which did not work as expected (arogachev)
- Bug #13198: Fixed order of checks in `yii\validators\IpValidator` that sometimes caused wrong error message (silverfire)
- Bug #13200: Creating URLs for routes specified in `yii\rest\UrlRule::$extraPatterns` did not work if no HTTP verb was specified (cebe)
- Bug #13212: Fixed `DbSession::regenerateID()` failure when `session_regenerate_id()` fails (andrewnester)
- Bug #13229: Fix fetching schema information for `pgsql` when `PDO::ATTR_CASE` is set (klimov-paul)
- Bug #13231: Fixed `destroy` method in `yii.gridView.js` which did not work as expected (arogachev)
- Bug #13232: Event handlers were not detached with changed selector in `yii.gridView.js` (arogachev)
- Bug #12969: Improved unique ID generation for `yii\widgets\Pjax` widgets (dynasource, samdark, rob006)
- Bug #13277: Fixed invalid parsing of `--` ("End of Options" special argument) in CLI (rugabarbo)
- Bug #13309: Fixes incorrect console width/height detecting with using Stty on Mac (nowm)
- Bug #13326: Fixed wrong background color generation in `BaseConsole::renderColoredString()` (nowm, silverfire)
- Bug #12133: Fixed `getDbTargets()` function in `yii\log\migrations\m141106_185632_log_init` that would create a log table correctly (bumstik)
- Bug #13416: Fixed `yii\web\MultipartFormDataParser` adds an extra newline to every value (klimov-paul)
- Enh #475: Added Bash and Zsh completion support for the `./yii` command (cebe, silverfire)
- Enh #6242: Access to validator in inline validation (arogachev)
- 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 #7820: Add `or` relation for `targetAttribute` in `yii\validators\UniqueValidator` (developeruz)
- Enh #9053: Added`yii\grid\RadioButtonColumn` (darwinisgod)
- Enh #9162: Added support of closures in `value` for attributes in `yii\widgets\DetailView` (arogachev)
- Enh #10896: Select only primary key when counting records in UniqueValidator (developeruz)
- Enh #10970: Allow omit specifying empty default params on URL creation (rob006)
- Enh #11037: `yii.js` and `yii.validation.js` use `Regexp.test()` instead of `String.match()` (arogachev, nkovacs)
- Enh #11163: Added separate method for client-side validation options `yii\validators\Validator::getClientOptions()` (arogachev)
- Enh #11697: Added `filterHaving()`, `andFilterHaving()` and `orFilterHaving()` to `yii\db\Query` (nicdnepr, samdark)
@ -106,14 +114,21 @@ Yii Framework 2 Change Log
- Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options (timbeks)
- Enh #13122: Optimized query for information about foreign keys in `yii\db\oci` (zlakomanoff)
- Enh #13202: Refactor validateAttribute method in UniqueValidator (developeruz)
- Enh #13268: Added logging of memory usage (bashkarev)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
- Enh: Refactored `yii\web\ErrorAction` to make it reusable (silverfire)
- Enh #13219: Enhancements for `yii\db\Connection` (Vovan-VE)
- Added `shuffleMasters` option which adds ability to disable shuffling of masters connections.
- Added `getMaster()` getter and `master` property for getting currently active master connection.
- Extracted `openFromPoolSequentially()` protected method from `openFromPool()` protected method.
- Enh #13264: Added `yii\widgets\InputWidget::$field` field, allowing access to the related `yii\widget\ActiveField` instance (klimov-paul)
- Enh #13266: Added `yii\validators\EachValidator::$stopOnFirstError` allowing addition of more than one error (klimov-paul)
- Enh #13268: Added logging of memory usage (bashkarev)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
- Enh: Refactored `yii\web\ErrorAction` to make it reusable (silverfire)
- Enh: Added support for field `yii\console\controllers\BaseMigrateController::$migrationNamespaces` setup from CLI (schmunk42)
- Bug #13287: Fixed translating "and" separator in `UniqueValidator` error message (jetexe)
- Enh #11464: Populate foreign key names from schema (joaoppereira)
- Bug #13401: Fixed lack of escaping of request dump at exception screens (samdark)
- Enh #13417: Allow customizing `yii\data\ActiveDataProvider` in `yii\rest\IndexAction` (leandrogehlen)
- Bug #12599: Fixed MSSQL fail to work with `nvarbinary`. Enhanced SQL scripts compatibility with older versions (samdark)
2.0.10 October 20, 2016
-----------------------

10
framework/UPGRADE.md

@ -26,13 +26,13 @@ as well as a stable version of Composer:
The simple way to upgrade Yii, for example to version 2.0.10 (replace this with the version you want) will be running `composer require`:
composer require "yiisoft/yii2:~2.0.10"
composer require "yiisoft/yii2:~2.0.10" --update-with-dependencies
This however may fail due to changes in the dependencies of yiisoft/yii2, which may change due to security updates
in other libraries or by adding support for newer versions. `composer require` will not update any other packages
as a safety feature.
This command will only upgrade Yii and its direct dependencies, if necessary. Without `--update-with-dependencies` the
upgrade might fail when the Yii version you chose has slightly different dependencies than the version you had before.
`composer require` will by default not update any other packages as a safety feature.
The better way to upgrade is to change the `composer.json` file to require the new Yii version and then
Another way to upgrade is to change the `composer.json` file to require the new Yii version and then
run `composer update` by specifying all packages that are allowed to be updated.
composer update yiisoft/yii2 yiisoft/yii2-composer bower-asset/jquery.inputmask

2
framework/base/Component.php

@ -390,7 +390,7 @@ class Component extends Object
*
* @param string $name the property name
* @param bool $checkBehaviors whether to treat behaviors' methods as methods of this component
* @return bool whether the property is defined
* @return bool whether the method is defined
*/
public function hasMethod($name, $checkBehaviors = true)
{

10
framework/caching/ArrayCache.php

@ -43,9 +43,8 @@ class ArrayCache extends Cache
{
if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) {
return $this->_cache[$key][0];
} else {
return false;
}
return false;
}
/**
@ -64,10 +63,9 @@ class ArrayCache extends Cache
{
if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) {
return false;
} else {
$this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration];
return true;
}
$this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration];
return true;
}
/**
@ -87,4 +85,4 @@ class ArrayCache extends Cache
$this->_cache = [];
return true;
}
}
}

1
framework/caching/DbDependency.php

@ -49,6 +49,7 @@ class DbDependency extends Dependency
*/
protected function generateDependencyData($cache)
{
/* @var $db Connection */
$db = Instance::ensure($this->db, Connection::class);
if ($this->sql === null) {
throw new InvalidConfigException('DbDependency::sql must be set.');

2
framework/caching/migrations/schema-mssql.sql

@ -8,6 +8,8 @@
* @license http://www.yiiframework.com/license/
* @since 2.0.7
*/
if object_id('[cache]', 'U') is not null
drop table [cache];
drop table if exists [cache];

2
framework/console/controllers/BaseMigrateController.php

@ -76,7 +76,7 @@ abstract class BaseMigrateController extends Controller
{
return array_merge(
parent::options($actionID),
['migrationPath'], // global for all actions
['migrationPath', 'migrationNamespaces'], // global for all actions
$actionID === 'create' ? ['templateFile'] : [] // action create
);
}

1
framework/db/cubrid/Schema.php

@ -159,7 +159,6 @@ class Schema extends \yii\db\Schema
];
}
}
$table->foreignKeys = array_values($table->foreignKeys);
return $table;
}

38
framework/db/mssql/QueryBuilder.php

@ -9,6 +9,7 @@ namespace yii\db\mssql;
use yii\base\InvalidArgumentException;
use yii\base\NotSupportedException;
use yii\db\Expression;
/**
* QueryBuilder is the query builder for MS SQL Server databases (version 2008 and above).
@ -314,4 +315,41 @@ class QueryBuilder extends \yii\db\QueryBuilder
{
return 'SELECT CASE WHEN EXISTS(' . $rawSql . ') THEN 1 ELSE 0 END';
}
/**
* Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary.
* @param string $table the table that data will be saved into.
* @param array $columns the column data (name => value) to be saved into the table.
* @return array normalized columns
*/
private function normalizeTableRowData($table, $columns, &$params)
{
if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) {
$columnSchemas = $tableSchema->columns;
foreach ($columns as $name => $value) {
// @see https://github.com/yiisoft/yii2/issues/12599
if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && $columnSchemas[$name]->dbType === 'varbinary' && is_string($value)) {
$phName = self::PARAM_PREFIX . count($params);
$columns[$name] = new Expression("CONVERT(VARBINARY, $phName)", [$phName => $value]);
}
}
}
return $columns;
}
/**
* @inheritdoc
*/
public function insert($table, $columns, &$params)
{
return parent::insert($table, $this->normalizeTableRowData($table, $columns, $params), $params);
}
/**
* @inheritdoc
*/
public function update($table, $columns, $condition, &$params)
{
return parent::update($table, $this->normalizeTableRowData($table, $columns, $params), $condition, $params);
}
}

4
framework/db/mssql/Schema.php

@ -362,6 +362,7 @@ SQL;
// http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx
$sql = <<<SQL
SELECT
[rc].[constraint_name] AS [fk_name],
[kcu1].[column_name] AS [fk_column_name],
[kcu2].[table_name] AS [uq_table_name],
[kcu2].[column_name] AS [uq_column_name]
@ -382,9 +383,10 @@ SQL;
':tableName' => $table->name,
':schemaName' => $table->schemaName,
])->queryAll();
$table->foreignKeys = [];
foreach ($rows as $row) {
$table->foreignKeys[] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']];
$table->foreignKeys[$row['fk_name']] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']];
}
}

6
framework/db/mysql/Schema.php

@ -269,13 +269,15 @@ SQL;
try {
$rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll();
$constraints = [];
foreach ($rows as $row) {
$constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name'];
$constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name'];
}
$table->foreignKeys = [];
foreach ($constraints as $constraint) {
$table->foreignKeys[] = array_merge(
foreach ($constraints as $name => $constraint) {
$table->foreignKeys[$name] = array_merge(
[$constraint['referenced_table_name']],
$constraint['columns']
);

6
framework/db/oci/Schema.php

@ -312,8 +312,12 @@ SQL;
}
$constraints[$name]['columns'][$row['COLUMN_NAME']] = $row['COLUMN_REF'];
}
foreach ($constraints as $constraint) {
$table->foreignKeys[] = array_merge([$constraint['tableName']], $constraint['columns']);
$name = array_keys($constraint);
$name = current($name);
$table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
}
}

4
framework/db/pgsql/Schema.php

@ -306,8 +306,8 @@ SQL;
}
$constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name'];
}
foreach ($constraints as $constraint) {
$table->foreignKeys[] = array_merge([$constraint['tableName']], $constraint['columns']);
foreach ($constraints as $name => $constraint) {
$table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
}
}

20
framework/helpers/BaseConsole.php

@ -528,9 +528,9 @@ class BaseConsole
'%4' => [self::BG_BLUE],
'%1' => [self::BG_RED],
'%5' => [self::BG_PURPLE],
'%6' => [self::BG_PURPLE],
'%7' => [self::BG_CYAN],
'%0' => [self::BG_GREY],
'%6' => [self::BG_CYAN],
'%7' => [self::BG_GREY],
'%0' => [self::BG_BLACK],
'%F' => [self::BLINK],
'%U' => [self::UNDERLINE],
'%8' => [self::NEGATIVE],
@ -619,8 +619,18 @@ class BaseConsole
} else {
// try stty if available
$stty = [];
if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches)) {
return $size = [(int)$matches[2], (int)$matches[1]];
if (exec('stty -a 2>&1', $stty)) {
$stty = implode(' ', $stty);
// Linux stty output
if (preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', $stty, $matches)) {
return $size = [(int)$matches[2], (int)$matches[1]];
}
// MacOS stty output
if (preg_match('/(\d+)\s+rows;\s*(\d+)\s+columns;/mi', $stty, $matches)) {
return $size = [(int)$matches[2], (int)$matches[1]];
}
}
// fallback to tput, which may not be updated on terminal resize

5
framework/helpers/BaseInflector.php

@ -566,8 +566,11 @@ class BaseInflector
* @return string the generated sentence
* @since 2.0.1
*/
public static function sentence(array $words, $twoWordsConnector = ' and ', $lastWordConnector = null, $connector = ', ')
public static function sentence(array $words, $twoWordsConnector = null, $lastWordConnector = null, $connector = ', ')
{
if ($twoWordsConnector === null) {
$twoWordsConnector = Yii::t('yii', ' and ');
}
if ($lastWordConnector === null) {
$lastWordConnector = $twoWordsConnector;
}

21
framework/helpers/BaseStringHelper.php

@ -284,4 +284,25 @@ class BaseStringHelper
{
return count(preg_split('/\s+/u', $string, null, PREG_SPLIT_NO_EMPTY));
}
/**
* Returns string represenation of number value with replaced commas to dots, if decimal point
* of current locale is comma
* @param int|float|string $value
* @return string
* @since 2.0.11
*/
public static function normalizeNumber($value)
{
$value = "$value";
$localeInfo = localeconv();
$decimalSeparator = isset($localeInfo['decimal_point']) ? $localeInfo['decimal_point'] : null;
if ($decimalSeparator !== null && $decimalSeparator !== '.') {
$value = str_replace($decimalSeparator, '.', $value);
}
return $value;
}
}

7
framework/i18n/migrations/schema-mssql.sql

@ -8,8 +8,11 @@
* @since 2.0.7
*/
drop table if exists [source_message];
drop table if exists [message];
if object_id('[source_message]', 'U') is not null
drop table [source_message];
if object_id('[message]', 'U') is not null
drop table [message];
CREATE TABLE [source_message]
(

13
framework/log/migrations/m141106_185632_log_init.php

@ -22,7 +22,7 @@ use yii\log\DbTarget;
class m141106_185632_log_init extends Migration
{
/**
* @var DbTarget[]
* @var DbTarget[] Targets to create log table for
*/
private $dbTargets = [];
@ -35,9 +35,18 @@ class m141106_185632_log_init extends Migration
if ($this->dbTargets === []) {
$log = Yii::$app->getLog();
$usedTargets = [];
foreach ($log->targets as $target) {
if ($target instanceof DbTarget) {
$this->dbTargets[] = $target;
$currentTarget = [
$target->db,
$target->logTable,
];
if (!in_array($currentTarget, $usedTargets, true)) {
// do not create same table twice
$usedTargets[] = $currentTarget;
$this->dbTargets[] = $target;
}
}
}

3
framework/log/migrations/schema-mssql.sql

@ -12,7 +12,8 @@
* @since 2.0.1
*/
drop table if exists [log];
if object_id('[log]', 'U') is not null
drop table [log];
create table [log]
(

13
framework/messages/ru/yii.php

@ -21,6 +21,7 @@ return [
'Unknown alias: -{name}' => 'Неизвестный псевдоним: -{name}',
'Yii Framework' => 'Yii Framework',
'(not set)' => '(не задано)',
' and ' => ' и ',
'An internal server error occurred.' => 'Возникла внутренняя ошибка сервера.',
'Are you sure you want to delete this item?' => 'Вы уверены, что хотите удалить этот элемент?',
'Error' => 'Ошибка',
@ -91,12 +92,12 @@ return [
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать минимум {min, number} {min, plural, one{символ} few{символа} many{символов} other{символа}}.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать максимум {max, number} {max, plural, one{символ} few{символа} many{символов} other{символа}}.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать {length, number} {length, plural, one{символ} few{символа} many{символов} other{символа}}.',
'{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, one{# день} few{# дня} many{# дней} other{# дней}}',
'{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, one{# час} few{# часа} many{# часов} other{# часов}}',
'{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, one{# минута} few{# минуты} many{# минут} other{# минут}}',
'{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, one{# месяц} few{# месяца} many{# месяцев} other{# месяцев}}',
'{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, one{# секунда} few{# секунды} many{# секунд} other{# секунд}}',
'{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, one{# год} few{# года} many{# лет} other{# лет}}',
'{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, one{# день} few{# дня} many{# дней} other{# дня}}',
'{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, one{# час} few{# часа} many{# часов} other{# часа}}',
'{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, one{# минута} few{# минуты} many{# минут} other{# минуты}}',
'{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, one{# месяц} few{# месяца} many{# месяцев} other{# месяца}}',
'{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, one{# секунда} few{# секунды} many{# секунд} other{# секунды}}',
'{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, one{# год} few{# года} many{# лет} other{# года}}',
'{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{день} one{# день} few{# дня} many{# дней} other{# дня}} назад',
'{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}} назад',
'{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}} назад',

15
framework/rbac/migrations/schema-mssql.sql

@ -9,10 +9,17 @@
* @since 2.0
*/
drop table [auth_assignment];
drop table [auth_item_child];
drop table [auth_item];
drop table [auth_rule];
if object_id('[auth_assignment]', 'U') is not null
drop table [auth_assignment];
if object_id('[auth_item_child]', 'U') is not null
drop table [auth_item_child];
if object_id('[auth_item]', 'U') is not null
drop table [auth_item];
if object_id('[auth_rule]', 'U') is not null
drop table [auth_rule];
create table [auth_rule]
(

3
framework/rest/IndexAction.php

@ -61,7 +61,8 @@ class IndexAction extends Action
/* @var $modelClass \yii\db\BaseActiveRecord */
$modelClass = $this->modelClass;
return new ActiveDataProvider([
return Yii::createObject([
'class' => ActiveDataProvider::className(),
'query' => $modelClass::find(),
]);
}

32
framework/validators/EachValidator.php

@ -60,6 +60,15 @@ class EachValidator extends Validator
* If disabled, own error message value will be used always.
*/
public $allowMessageFromRule = true;
/**
* @var bool whether to stop validation once first error among attribute value elements is detected.
* When enabled validation will produce single error message on attribute, when disabled - multiple
* error messages mya appear: one per each invalid value.
* Note that this option will affect only [[validateAttribute()]] value, while [[validateValue()]] will
* not be affected.
* @since 2.0.11
*/
public $stopOnFirstError = true;
/**
* @var Validator validator instance.
@ -125,30 +134,35 @@ class EachValidator extends Validator
$validator = $this->getValidator($model); // ensure model context while validator creation
$originalErrors = $model->getErrors($attribute);
$filteredValue = [];
$detectedErrors = $model->getErrors($attribute);
$filteredValue = $model->$attribute;
foreach ($value as $k => $v) {
$model->clearErrors($attribute);
$model->$attribute = $v;
if (!$validator->skipOnEmpty || !$validator->isEmpty($v)) {
$validator->validateAttribute($model, $attribute);
}
$filteredValue[$k] = $model->$attribute;
if ($model->hasErrors($attribute)) {
$validationErrors = $model->getErrors($attribute);
$model->clearErrors($attribute);
if (!empty($originalErrors)) {
$model->addErrors([$attribute => $originalErrors]);
}
if ($this->allowMessageFromRule) {
$model->addErrors([$attribute => $validationErrors]);
$validationErrors = $model->getErrors($attribute);
$detectedErrors = array_merge($detectedErrors, $validationErrors);
} else {
$model->clearErrors($attribute);
$this->addError($model, $attribute, $this->message, ['value' => $v]);
$detectedErrors[] = $model->getFirstError($attribute);
}
$model->$attribute = $value;
return;
if ($this->stopOnFirstError) {
break;
}
}
}
$model->$attribute = $filteredValue;
$model->clearErrors($attribute);
$model->addErrors([$attribute => $detectedErrors]);
}
/**

61
framework/validators/ExistValidator.php

@ -49,9 +49,8 @@ class ExistValidator extends Validator
* @var string|array the name of the ActiveRecord attribute that should be used to
* validate the existence of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the existence
* of multiple columns at the same time. The array values are the attributes that will be
* used to validate the existence, while the array keys are the attributes whose values are to be validated.
* If the key and the value are the same, you can just specify the value.
* of multiple columns at the same time. The array key is the name of the attribute with the value to validate,
* the array value is the name of the database field to search.
*/
public $targetAttribute;
/**
@ -66,6 +65,11 @@ class ExistValidator extends Validator
*/
public $allowArray = false;
/**
* @var string and|or define how target attributes are related
* @since 2.0.11
*/
public $targetAttributeJunction = 'and';
/**
* @inheritdoc
@ -84,31 +88,24 @@ class ExistValidator extends Validator
public function validateAttribute($model, $attribute)
{
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
if (is_array($targetAttribute)) {
if ($this->allowArray) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
$params = [];
foreach ($targetAttribute as $k => $v) {
$params[$v] = is_int($k) ? $model->$v : $model->$k;
}
} else {
$params = [$targetAttribute => $model->$attribute];
}
$params = $this->prepareConditions($targetAttribute, $model, $attribute);
$conditions[] = $this->targetAttributeJunction == 'or' ? 'or' : 'and';
if (!$this->allowArray) {
foreach ($params as $value) {
foreach ($params as $key => $value) {
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
$conditions[] = [$key => $value];
}
} else {
$conditions[] = $params;
}
$targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass;
$query = $this->createQuery($targetClass, $params);
$query = $this->createQuery($targetClass, $conditions);
if (is_array($model->$attribute)) {
if ($query->count("DISTINCT [[$targetAttribute]]") != count($model->$attribute)) {
@ -120,6 +117,36 @@ class ExistValidator extends Validator
}
/**
* Processes attributes' relations described in $targetAttribute parameter into conditions, compatible with
* [[\yii\db\Query::where()|Query::where()]] key-value format.
*
* @param $targetAttribute array|string $attribute the name of the ActiveRecord attribute that should be used to
* validate the existence of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the existence
* of multiple columns at the same time. The array key is the name of the attribute with the value to validate,
* the array value is the name of the database field to search.
* @param \yii\base\Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated in the $model
* @return array conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
* @throws InvalidConfigException
*/
private function prepareConditions($targetAttribute, $model, $attribute)
{
if (is_array($targetAttribute)) {
if ($this->allowArray) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
$params = [];
foreach ($targetAttribute as $k => $v) {
$params[$v] = is_int($k) ? $model->$attribute : $model->$k;
}
} else {
$params = [$targetAttribute => $model->$attribute];
}
return $params;
}
/**
* @inheritdoc
*/
protected function validateValue($value)

6
framework/validators/NumberValidator.php

@ -8,6 +8,7 @@
namespace yii\validators;
use Yii;
use yii\helpers\StringHelper;
use yii\web\JsExpression;
use yii\helpers\Json;
@ -85,7 +86,8 @@ class NumberValidator extends Validator
return;
}
$pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
if (!preg_match($pattern, "$value")) {
if (!preg_match($pattern, StringHelper::normalizeNumber($value))) {
$this->addError($model, $attribute, $this->message);
}
if ($this->min !== null && $value < $this->min) {
@ -105,7 +107,7 @@ class NumberValidator extends Validator
return [Yii::t('yii', '{attribute} is invalid.'), []];
}
$pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
if (!preg_match($pattern, "$value")) {
if (!preg_match($pattern, StringHelper::normalizeNumber($value))) {
return [$this->message, []];
} elseif ($this->min !== null && $value < $this->min) {
return [$this->tooSmall, ['min' => $this->min]];

19
framework/validators/UniqueValidator.php

@ -57,7 +57,6 @@ class UniqueValidator extends Validator
* of the attribute currently being validated. You may use an array to validate the uniqueness
* of multiple columns at the same time. The array values are the attributes that will be
* used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
* If the key and the value are the same, you can just specify the value.
*/
public $targetAttribute;
/**
@ -89,6 +88,11 @@ class UniqueValidator extends Validator
*/
public $comboNotUnique;
/**
* @var string and|or define how target attributes are related
* @since 2.0.11
*/
public $targetAttributeJunction = 'and';
/**
* @inheritdoc
@ -118,15 +122,16 @@ class UniqueValidator extends Validator
{
/* @var $targetClass ActiveRecordInterface */
$targetClass = $this->targetClass ?: get_class($model);
$targetClass = ltrim($targetClass, '\\');
$targetAttribute = $this->targetAttribute ?: $attribute;
$conditions = $this->prepareConditions($targetAttribute, $model, $attribute);
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
$rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute);
$conditions[] = $this->targetAttributeJunction == 'or' ? 'or' : 'and';
foreach ($conditions as $value) {
foreach ($rawConditions as $key => $value) {
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
$conditions[] = [$key => $value];
}
if ($this->modelExists($targetClass, $conditions, $model)) {
@ -199,7 +204,6 @@ class UniqueValidator extends Validator
{
$query = $targetClass::find();
$query->andWhere($conditions);
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $query);
} elseif ($this->filter !== null) {
@ -217,7 +221,6 @@ class UniqueValidator extends Validator
* should be used to validate the uniqueness of the current attribute value. You may use an array to validate
* the uniqueness of multiple columns at the same time. The array values are the attributes that will be
* used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
* If the key and the value are the same, you can just specify the value.
* @param Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated in the $model
@ -228,7 +231,7 @@ class UniqueValidator extends Validator
if (is_array($targetAttribute)) {
$conditions = [];
foreach ($targetAttribute as $k => $v) {
$conditions[$v] = is_int($k) ? $model->$v : $model->$k;
$conditions[$v] = is_int($k) ? $model->$attribute : $model->$k;
}
} else {
$conditions = [$targetAttribute => $model->$attribute];

5
framework/web/DbSession.php

@ -102,6 +102,11 @@ class DbSession extends MultiFieldSession
parent::regenerateID(false);
$newID = session_id();
// if session id regeneration failed, no need to create/update it.
if (empty($newID)) {
Yii::warning('Failed to generate new session ID', __METHOD__);
return;
}
$query = new Query();
$row = $query->from($this->sessionTable)

2
framework/web/ErrorHandler.php

@ -323,7 +323,7 @@ class ErrorHandler extends \yii\base\ErrorHandler
}
}
return '<pre>' . rtrim($request, "\n") . '</pre>';
return '<pre>' . $this->htmlEncode(rtrim($request, "\n")) . '</pre>';
}
/**

2
framework/web/MultipartFormDataParser.php

@ -132,7 +132,7 @@ class MultipartFormDataParser extends Object implements RequestParserInterface
}
$boundary = $matches[1];
$bodyParts = preg_split('/-+' . preg_quote($boundary) . '/s', $rawBody);
$bodyParts = preg_split('/\\R?-+' . preg_quote($boundary) . '/s', $rawBody);
array_pop($bodyParts); // last block always has no data, contains boundary ending like `--`
$bodyParams = [];

11
framework/web/UrlRule.php

@ -367,8 +367,15 @@ class UrlRule extends Object implements UrlRuleInterface
continue;
}
if (!isset($params[$name])) {
return false;
} elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
// allow omit empty optional params
// @see https://github.com/yiisoft/yii2/issues/10970
if (in_array($name, $this->placeholders) && strcmp($value, '') === 0) {
$params[$name] = '';
} else {
return false;
}
}
if (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
unset($params[$name]);
if (isset($this->_paramRules[$name])) {
$tr["<$name>"] = '';

3
framework/web/migrations/schema-mssql.sql

@ -9,7 +9,8 @@
* @since 2.0.8
*/
drop table if exists [session];
if object_id('[session]', 'U') is not null
drop table [session];
create table [session]
(

10
framework/widgets/ActiveField.php

@ -714,10 +714,14 @@ class ActiveField extends Component
$config['model'] = $this->model;
$config['attribute'] = $this->attribute;
$config['view'] = $this->form->getView();
if (isset($config['options']) && isset(class_parents($class)['yii\widgets\InputWidget'])) {
$this->addAriaAttributes($config['options']);
$this->adjustLabelFor($config['options']);
if (is_subclass_of($class, 'yii\widgets\InputWidget')) {
$config['field'] = $this;
if (isset($config['options'])) {
$this->addAriaAttributes($config['options']);
$this->adjustLabelFor($config['options']);
}
}
$this->parts['{input}'] = $class::widget($config);
return $this;

6
framework/widgets/InputWidget.php

@ -37,6 +37,12 @@ use yii\helpers\Html;
class InputWidget extends Widget
{
/**
* @var \yii\widgets\ActiveField active input field, which triggers this widget rendering.
* This field will be automatically filled up in case widget instance is created via [[\yii\widgets\ActiveField::widget()]].
* @since 2.0.11
*/
public $field;
/**
* @var Model the data model that this widget is associated with.
*/
public $model;

210
tests/framework/behaviors/BlameableBehaviorTest.php

@ -0,0 +1,210 @@
<?php
namespace yiiunit\framework\behaviors;
use Yii;
use yii\base\Object;
use yii\behaviors\BlameableBehavior;
use yii\db\BaseActiveRecord;
use yiiunit\TestCase;
use yii\db\Connection;
use yii\db\ActiveRecord;
/**
* Unit test for [[\yii\behaviors\BlameableBehavior]].
*
* @group behaviors
*/
class BlameableBehaviorTest extends TestCase
{
public static function setUpBeforeClass()
{
if (!extension_loaded('pdo') || !extension_loaded('pdo_sqlite')) {
static::markTestSkipped('PDO and SQLite extensions are required.');
}
}
public function setUp()
{
$this->mockApplication([
'components' => [
'db' => [
'class' => '\yii\db\Connection',
'dsn' => 'sqlite::memory:',
],
'user' => [
'class' => 'yiiunit\framework\behaviors\UserMock',
]
]
]);
$columns = [
'name' => 'string',
'created_by' => 'integer',
'updated_by' => 'integer',
];
Yii::$app->getDb()->createCommand()->createTable('test_blame', $columns)->execute();
$this->getUser()->login(10);
}
/**
* @return UserMock
*/
private function getUser()
{
return Yii::$app->get('user');
}
public function tearDown()
{
Yii::$app->getDb()->close();
parent::tearDown();
}
public function testInsertUserIsGuest()
{
$this->getUser()->logout();
$model = new ActiveRecordBlameable();
$model->name = __METHOD__;
$model->beforeSave(true);
$this->assertNull($model->created_by);
$this->assertNull($model->updated_by);
}
public function testInsertUserIsNotGuest()
{
$model = new ActiveRecordBlameable();
$model->name = __METHOD__;
$model->beforeSave(true);
$this->assertEquals(10, $model->created_by);
$this->assertEquals(10, $model->updated_by);
}
public function testUpdateUserIsNotGuest()
{
$model = new ActiveRecordBlameable();
$model->name = __METHOD__;
$model->save();
$this->getUser()->login(20);
$model = ActiveRecordBlameable::findOne(['name' => __METHOD__]);
$model->name = __CLASS__;
$model->save();
$this->assertEquals(10, $model->created_by);
$this->assertEquals(20, $model->updated_by);
}
public function testInsertCustomValue()
{
$model = new ActiveRecordBlameable();
$model->name = __METHOD__;
$model->getBlameable()->value = 42;
$model->beforeSave(true);
$this->assertEquals(42, $model->created_by);
$this->assertEquals(42, $model->updated_by);
}
public function testInsertClosure()
{
$model = new ActiveRecordBlameable();
$model->name = __METHOD__;
$model->getBlameable()->value = function ($event) {
return strlen($event->sender->name); // $model->name;
};
$model->beforeSave(true);
$this->assertEquals(strlen($model->name), $model->created_by);
$this->assertEquals(strlen($model->name), $model->updated_by);
}
public function testCustomAttributesAndEvents()
{
$model = new ActiveRecordBlameable([
'as blameable' => [
'class' => BlameableBehavior::className(),
'attributes' => [
BaseActiveRecord::EVENT_BEFORE_VALIDATE => 'created_by',
BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_by', 'updated_by']
]
]
]);
$model->name = __METHOD__;
$this->assertNull($model->created_by);
$this->assertNull($model->updated_by);
$model->beforeValidate();
$this->assertEquals(10, $model->created_by);
$this->assertNull($model->updated_by);
$this->getUser()->login(20);
$model->beforeSave(true);
$this->assertEquals(20, $model->created_by);
$this->assertEquals(20, $model->updated_by);
}
}
/**
* Test Active Record class with [[BlameableBehavior]] behavior attached.
*
* @property string $name
* @property int $created_by
* @property int $updated_by
*
* @property BlameableBehavior $blameable
*/
class ActiveRecordBlameable extends ActiveRecord
{
public function behaviors()
{
return [
'blameable' => [
'class' => BlameableBehavior::className(),
],
];
}
public static function tableName()
{
return 'test_blame';
}
/**
* @return BlameableBehavior
*/
public function getBlameable()
{
return $this->getBehavior('blameable');
}
public static function primaryKey()
{
return ['name'];
}
}
class UserMock extends Object
{
public $id;
public $isGuest = true;
public function login($id)
{
$this->isGuest = false;
$this->id = $id;
}
public function logout()
{
$this->isGuest = true;
$this->id = null;
}
}

54
tests/framework/caching/DbDependencyTest.php

@ -0,0 +1,54 @@
<?php
namespace yiiunit\framework\caching;
use yii\caching\ArrayCache;
use yii\caching\DbDependency;
use yiiunit\framework\db\DatabaseTestCase;
/**
* @group caching
*/
class DbDependencyTest extends DatabaseTestCase
{
/**
* @inheritdoc
*/
protected $driverName = 'sqlite';
/**
* @inheritdoc
*/
protected function setUp()
{
parent::setUp();
$db = $this->getConnection(false);
$db->createCommand()->createTable('dependency_item', [
'id' => 'pk',
'value' => 'string',
])->execute();
$db->createCommand()->insert('dependency_item', ['value' => 'initial'])->execute();
}
public function testIsChanged()
{
$db = $this->getConnection(false);
$cache = new ArrayCache();
$dependency = new DbDependency();
$dependency->db = $db;
$dependency->sql = 'SELECT [[id]] FROM {{dependency_item}} ORDER BY [[id]] DESC LIMIT 1';
$dependency->reusable = false;
$dependency->evaluateDependency($cache);
$this->assertFalse($dependency->isChanged($cache));
$db->createCommand()->insert('dependency_item', ['value' => 'new'])->execute();
$this->assertTrue($dependency->isChanged($cache));
}
}

6
tests/framework/caching/DependencyTest.php

@ -1,6 +1,6 @@
<?php
namespace yiiunit\framework\caching;
namespace yiiunit\framework\caching;
use yii\caching\Cache;
use yii\caching\Dependency;
@ -37,7 +37,7 @@ class DependencyTest extends TestCase
$this->assertEquals(40, strlen($result));
}
public function testisChanged()
public function testIsChanged()
{
$dependency = $this->getMockForAbstractClass(Dependency::class);
$cache = $this->getMockForAbstractClass(Cache::class);
@ -49,4 +49,4 @@ class DependencyTest extends TestCase
$result = $dependency->isChanged($cache);
$this->assertTrue($result);
}
}
}

8
tests/framework/db/SchemaTest.php

@ -112,10 +112,10 @@ abstract class SchemaTest extends DatabaseTestCase
$table = $schema->getTableSchema('composite_fk');
$this->assertCount(1, $table->foreignKeys);
$this->assertTrue(isset($table->foreignKeys[0]));
$this->assertEquals('order_item', $table->foreignKeys[0][0]);
$this->assertEquals('order_id', $table->foreignKeys[0]['order_id']);
$this->assertEquals('item_id', $table->foreignKeys[0]['item_id']);
$this->assertTrue(isset($table->foreignKeys['FK_composite_fk_order_item']));
$this->assertEquals('order_item', $table->foreignKeys['FK_composite_fk_order_item'][0]);
$this->assertEquals('order_id', $table->foreignKeys['FK_composite_fk_order_item']['order_id']);
$this->assertEquals('item_id', $table->foreignKeys['FK_composite_fk_order_item']['item_id']);
}
public function testGetPDOType()

14
tests/framework/db/pgsql/SchemaTest.php

@ -81,6 +81,20 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
return $columns;
}
public function testCompositeFk()
{
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
$table = $schema->getTableSchema('composite_fk');
$this->assertCount(1, $table->foreignKeys);
$this->assertTrue(isset($table->foreignKeys['fk_composite_fk_order_item']));
$this->assertEquals('order_item', $table->foreignKeys['fk_composite_fk_order_item'][0]);
$this->assertEquals('order_id', $table->foreignKeys['fk_composite_fk_order_item']['order_id']);
$this->assertEquals('item_id', $table->foreignKeys['fk_composite_fk_order_item']['item_id']);
}
public function testGetPDOType()
{
$values = [

13
tests/framework/db/sqlite/SchemaTest.php

@ -29,4 +29,17 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
return $columns;
}
public function testCompositeFk()
{
/* @var $schema Schema */
$schema = $this->getConnection()->schema;
$table = $schema->getTableSchema('composite_fk');
$this->assertCount(1, $table->foreignKeys);
$this->assertTrue(isset($table->foreignKeys[0]));
$this->assertEquals('order_item', $table->foreignKeys[0][0]);
$this->assertEquals('order_id', $table->foreignKeys[0]['order_id']);
$this->assertEquals('item_id', $table->foreignKeys[0]['item_id']);
}
}

24
tests/framework/validators/EachValidatorTest.php

@ -155,4 +155,26 @@ class EachValidatorTest extends TestCase
$validator->validateAttribute($model, 'attr_one');
$this->assertEmpty($model->getErrors('attr_one'));
}
}
/**
* @depends testValidate
*/
public function testStopOnFirstError()
{
$model = FakedValidationModel::createWithAttributes([
'attr_one' => [
'one', 2, 'three'
],
]);
$validator = new EachValidator(['rule' => ['integer']]);
$validator->stopOnFirstError = true;
$validator->validateAttribute($model, 'attr_one');
$this->assertCount(1, $model->getErrors('attr_one'));
$model->clearErrors();
$validator->stopOnFirstError = false;
$validator->validateAttribute($model, 'attr_one');
$this->assertCount(2, $model->getErrors('attr_one'));
}
}

68
tests/framework/validators/NumberValidatorTest.php

@ -12,10 +12,42 @@ use yiiunit\TestCase;
*/
class NumberValidatorTest extends TestCase
{
private $commaDecimalLocales = ['fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252'];
private $pointDecimalLocales = ['en_US.UTF-8', 'en_US.UTF8', 'en_US.utf-8', 'en_US.utf8', 'English_United States.1252'];
private $oldLocale;
private function setCommaDecimalLocale()
{
if ($this->oldLocale === false) {
$this->markTestSkipped('Your platform does not support locales.');
}
if (setlocale(LC_NUMERIC, $this->commaDecimalLocales) === false) {
$this->markTestSkipped('Could not set any of required locales: ' . implode(', ', $this->commaDecimalLocales));
}
}
private function setPointDecimalLocale()
{
if ($this->oldLocale === false) {
$this->markTestSkipped('Your platform does not support locales.');
}
if (setlocale(LC_NUMERIC, $this->pointDecimalLocales) === false) {
$this->markTestSkipped('Could not set any of required locales: ' . implode(', ', $this->pointDecimalLocales));
}
}
private function restoreLocale()
{
setlocale(LC_NUMERIC, $this->oldLocale);
}
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->oldLocale = setlocale(LC_NUMERIC, 0);
}
public function testEnsureMessageOnInit()
@ -37,7 +69,13 @@ class NumberValidatorTest extends TestCase
$this->assertTrue($val->validate(-20));
$this->assertTrue($val->validate('20'));
$this->assertTrue($val->validate(25.45));
$this->setPointDecimalLocale();
$this->assertFalse($val->validate('25,45'));
$this->setCommaDecimalLocale();
$this->assertTrue($val->validate('25,45'));
$this->restoreLocale();
$this->assertFalse($val->validate('12:45'));
$val = new NumberValidator(['integerOnly' => true]);
$this->assertTrue($val->validate(20));
@ -70,6 +108,19 @@ class NumberValidatorTest extends TestCase
$this->assertFalse($val->validate('12.23^4'));
}
public function testValidateValueWithLocaleWhereDecimalPointIsComma()
{
$val = new NumberValidator();
$this->setPointDecimalLocale();
$this->assertTrue($val->validate(.5));
$this->setCommaDecimalLocale();
$this->assertTrue($val->validate(.5));
$this->restoreLocale();
}
public function testValidateValueMin()
{
$val = new NumberValidator(['min' => 1]);
@ -159,6 +210,23 @@ class NumberValidatorTest extends TestCase
}
public function testValidateAttributeWithLocaleWhereDecimalPointIsComma()
{
$val = new NumberValidator();
$model = new FakedValidationModel();
$model->attr_number = 0.5;
$this->setPointDecimalLocale();
$val->validateAttribute($model, 'attr_number');
$this->assertFalse($model->hasErrors('attr_number'));
$this->setCommaDecimalLocale();
$val->validateAttribute($model, 'attr_number');
$this->assertFalse($model->hasErrors('attr_number'));
$this->restoreLocale();
}
public function testEnsureCustomMessageIsSetOnValidateAttribute()
{
$val = new NumberValidator([

9
tests/framework/validators/UniqueValidatorTest.php

@ -80,7 +80,7 @@ abstract class UniqueValidatorTest extends DatabaseTestCase
$customerModel->name = 'test data';
$customerModel->email = ['email@mail.com', 'email2@mail.com',];
$validator->targetAttribute = ['email', 'name'];
$validator->targetAttribute = ['email' => 'email', 'name' => 'name'];
$validator->validateAttribute($customerModel, 'name');
$this->assertEquals($messageError, $customerModel->getFirstError('name'));
}
@ -149,7 +149,7 @@ abstract class UniqueValidatorTest extends DatabaseTestCase
{
$val = new UniqueValidator([
'targetClass' => OrderItem::class,
'targetAttribute' => ['order_id', 'item_id'],
'targetAttribute' => ['order_id', 'item_id' => 'item_id'],
]);
// validate old record
/** @var OrderItem $m */
@ -319,7 +319,7 @@ abstract class UniqueValidatorTest extends DatabaseTestCase
$targetAttribute = ['val_attr_b', 'val_attr_c'];
$result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]);
$expected = ['val_attr_b' => 'test value b', 'val_attr_c' => 'test value c'];
$expected = ['val_attr_b' => 'test value a', 'val_attr_c' => 'test value a'];
$this->assertEquals($expected, $result);
$targetAttribute = ['val_attr_a' => 'val_attr_b'];
@ -327,10 +327,9 @@ abstract class UniqueValidatorTest extends DatabaseTestCase
$expected = ['val_attr_b' => 'test value a'];
$this->assertEquals($expected, $result);
$targetAttribute = ['val_attr_b', 'val_attr_a' => 'val_attr_c'];
$result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]);
$expected = ['val_attr_b' => 'test value b', 'val_attr_c' => 'test value a'];
$expected = ['val_attr_b' => 'test value a', 'val_attr_c' => 'test value a'];
$this->assertEquals($expected, $result);
}

64
tests/framework/web/ControllerTest.php

@ -8,13 +8,11 @@
namespace yiiunit\framework\web;
use Yii;
use yii\web\Controller;
use yii\base\InlineAction;
use yii\web\Response;
use yiiunit\TestCase;
use yiiunit\framework\di\stubs\Qux;
use yiiunit\framework\web\stubs\Bar;
use yiiunit\framework\web\stubs\OtherQux;
use yii\base\InlineAction;
use yiiunit\TestCase;
/**
* @group web
@ -24,18 +22,16 @@ class ControllerTest extends TestCase
public function testBindActionParams()
{
$this->mockApplication();
$controller = new FakeController('fake', Yii::$app);
$aksi1 = new InlineAction('aksi1', $controller, 'actionAksi1');
$aksi1 = new InlineAction('aksi1', $this->controller, 'actionAksi1');
$params = ['fromGet'=>'from query params','q'=>'d426','validator'=>'avaliable'];
list($fromGet, $other) = $controller->bindActionParams($aksi1, $params);
$params = ['fromGet' => 'from query params', 'q' => 'd426', 'validator' => 'avaliable'];
list($fromGet, $other) = $this->controller->bindActionParams($aksi1, $params);
$this->assertEquals('from query params', $fromGet);
$this->assertEquals('default', $other);
$params = ['fromGet'=>'from query params','q'=>'d426','other'=>'avaliable'];
list($fromGet, $other) = $controller->bindActionParams($aksi1, $params);
$params = ['fromGet' => 'from query params', 'q' => 'd426', 'other' => 'avaliable'];
list($fromGet, $other) = $this->controller->bindActionParams($aksi1, $params);
$this->assertEquals('from query params', $fromGet);
$this->assertEquals('avaliable', $other);
@ -43,14 +39,11 @@ class ControllerTest extends TestCase
public function testAsJson()
{
$this->mockWebApplication();
$controller = new Controller('test', Yii::$app);
$data = [
'test' => 123,
'example' => 'data',
];
$result = $controller->asJson($data);
$result = $this->controller->asJson($data);
$this->assertInstanceOf('yii\web\Response', $result);
$this->assertSame(Yii::$app->response, $result, 'response should be the same as Yii::$app->response');
$this->assertEquals(Response::FORMAT_JSON, $result->format);
@ -59,18 +52,51 @@ class ControllerTest extends TestCase
public function testAsXml()
{
$this->mockWebApplication();
$controller = new Controller('test', Yii::$app);
$data = [
'test' => 123,
'example' => 'data',
];
$result = $controller->asXml($data);
$result = $this->controller->asXml($data);
$this->assertInstanceOf('yii\web\Response', $result);
$this->assertSame(Yii::$app->response, $result, 'response should be the same as Yii::$app->response');
$this->assertEquals(Response::FORMAT_XML, $result->format);
$this->assertEquals($data, $result->data);
}
public function testRedirect()
{
$_SERVER['REQUEST_URI'] = 'http://test-domain.com/';
$this->assertEquals($this->controller->redirect('')->headers->get('location'), '/');
$this->assertEquals($this->controller->redirect('http://some-external-domain.com')->headers->get('location'), 'http://some-external-domain.com');
$this->assertEquals($this->controller->redirect('/')->headers->get('location'), '/');
$this->assertEquals($this->controller->redirect('/something-relative')->headers->get('location'), '/something-relative');
$this->assertEquals($this->controller->redirect(['/'])->headers->get('location'), '/index.php?r=');
$this->assertEquals($this->controller->redirect(['view'])->headers->get('location'), '/index.php?r=fake%2Fview');
$this->assertEquals($this->controller->redirect(['/controller'])->headers->get('location'), '/index.php?r=controller');
$this->assertEquals($this->controller->redirect(['/controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->controller->redirect(['//controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->controller->redirect(['//controller/index', 'id' => 3])->headers->get('location'), '/index.php?r=controller%2Findex&id=3');
$this->assertEquals($this->controller->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->headers->get('location'), '/index.php?r=controller%2Findex&id_1=3&id_2=4');
$this->assertEquals($this->controller->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->headers->get('location'), '/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29');
}
protected function setUp()
{
parent::setUp();
$this->controller = new FakeController('fake', new \yii\web\Application([
'id' => 'app',
'basePath' => __DIR__,
'components' => [
'request' => [
'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq',
'scriptFile' => __DIR__ . '/index.php',
'scriptUrl' => '/index.php',
],
]
]));
$this->mockWebApplication(['controller' => $this->controller]);
}
}

8
tests/framework/web/MultipartFormDataParserTest.php

@ -18,10 +18,10 @@ class MultipartFormDataParserTest extends TestCase
$boundary = '---------------------------22472926011618';
$contentType = 'multipart/form-data; boundary=' . $boundary;
$rawBody = "--{$boundary}\nContent-Disposition: form-data; name=\"title\"\r\n\r\ntest-title";
$rawBody .= "--{$boundary}\nContent-Disposition: form-data; name=\"Item[name]\"\r\n\r\ntest-name";
$rawBody .= "--{$boundary}\nContent-Disposition: form-data; name=\"someFile\"; filename=\"some-file.txt\"\nContent-Type: text/plain\r\n\r\nsome file content";
$rawBody .= "--{$boundary}\nContent-Disposition: form-data; name=\"Item[file]\"; filename=\"item-file.txt\"\nContent-Type: text/plain\r\n\r\nitem file content";
$rawBody .= "--{$boundary}--";
$rawBody .= "\r\n--{$boundary}\nContent-Disposition: form-data; name=\"Item[name]\"\r\n\r\ntest-name";
$rawBody .= "\r\n--{$boundary}\nContent-Disposition: form-data; name=\"someFile\"; filename=\"some-file.txt\"\nContent-Type: text/plain\r\n\r\nsome file content";
$rawBody .= "\r\n--{$boundary}\nContent-Disposition: form-data; name=\"Item[file]\"; filename=\"item-file.txt\"\nContent-Type: text/plain\r\n\r\nitem file content";
$rawBody .= "\r\n--{$boundary}--";
$bodyParams = $parser->parse($rawBody, $contentType);

19
tests/framework/web/ResponseTest.php

@ -18,7 +18,7 @@ class ResponseTest extends \yiiunit\TestCase
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->mockWebApplication();
$this->response = new \yii\web\Response;
}
@ -101,4 +101,21 @@ class ResponseTest extends \yiiunit\TestCase
static::assertEquals('attachment; filename="test.txt"', $headers->get('Content-Disposition'));
static::assertEquals(4, $headers->get('Content-Length'));
}
public function testRedirect()
{
$_SERVER['REQUEST_URI'] = 'http://test-domain.com/';
$this->assertEquals($this->response->redirect('')->headers->get('location'), '/');
$this->assertEquals($this->response->redirect('http://some-external-domain.com')->headers->get('location'), 'http://some-external-domain.com');
$this->assertEquals($this->response->redirect('/')->headers->get('location'), '/');
$this->assertEquals($this->response->redirect('/something-relative')->headers->get('location'), '/something-relative');
$this->assertEquals($this->response->redirect(['/'])->headers->get('location'), '/index.php?r=');
$this->assertEquals($this->response->redirect(['view'])->headers->get('location'), '/index.php?r=view');
$this->assertEquals($this->response->redirect(['/controller'])->headers->get('location'), '/index.php?r=controller');
$this->assertEquals($this->response->redirect(['/controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->response->redirect(['//controller/index'])->headers->get('location'), '/index.php?r=controller%2Findex');
$this->assertEquals($this->response->redirect(['//controller/index', 'id' => 3])->headers->get('location'), '/index.php?r=controller%2Findex&id=3');
$this->assertEquals($this->response->redirect(['//controller/index', 'id_1' => 3, 'id_2' => 4])->headers->get('location'), '/index.php?r=controller%2Findex&id_1=3&id_2=4');
$this->assertEquals($this->response->redirect(['//controller/index', 'slug' => 'äöüß!"§$%&/()'])->headers->get('location'), '/index.php?r=controller%2Findex&slug=%C3%A4%C3%B6%C3%BC%C3%9F%21%22%C2%A7%24%25%26%2F%28%29');
}
}

59
tests/framework/web/UrlRuleTest.php

@ -31,7 +31,7 @@ class UrlRuleTest extends TestCase
foreach ($tests as $j => $test) {
list ($route, $params, $expected) = $test;
$url = $rule->createUrl($manager, $route, $params);
$this->assertEquals($expected, $url, "Test#$i-$j: $name");
$this->assertSame($expected, $url, "Test#$i-$j: $name");
}
}
}
@ -483,6 +483,63 @@ class UrlRuleTest extends TestCase
],
],
[
'optional params - example from guide',
[
'pattern' => 'posts/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1, 'tag' => ''],
],
[
['post/index', ['page' => 1, 'tag' => ''], 'posts'],
['post/index', ['page' => 2, 'tag' => ''], 'posts/2'],
['post/index', ['page' => 2, 'tag' => 'news'], 'posts/2/news'],
['post/index', ['page' => 1, 'tag' => 'news'], 'posts/news'],
// allow skip empty params on URL creation
['post/index', [], false],
['post/index', ['tag' => ''], false],
['post/index', ['page' => 1], 'posts'],
['post/index', ['page' => 2], 'posts/2'],
],
],
[
'required params',
[
'pattern' => 'about-me',
'route' => 'site/page',
'defaults' => ['id' => 1],
],
[
['site/page', ['id' => 1], 'about-me'],
['site/page', ['id' => 2], false],
],
],
[
'required default param',
[
'pattern' => '',
'route' => 'site/home',
'defaults' => ['lang' => 'en'],
],
[
['site/home', ['lang' => 'en'], ''],
['site/home', ['lang' => ''], false],
['site/home', [], false],
],
],
[
'required default empty param',
[
'pattern' => '',
'route' => 'site/home',
'defaults' => ['lang' => ''],
],
[
['site/home', ['lang' => ''], ''],
['site/home', ['lang' => 'en'], false],
['site/home', [], false],
],
],
[
'route has parameters',
[
'pattern' => '<controller>/<action>',

39
tests/framework/widgets/ActiveFieldTest.php

@ -8,6 +8,7 @@ use yii\base\DynamicModel;
use yii\widgets\ActiveForm;
use yii\web\View;
use yii\web\AssetManager;
use yii\widgets\InputWidget;
/**
* @author Nelson J Morais <njmorais@gmail.com>
@ -147,7 +148,8 @@ EOD;
$this->assertEquals($expectedValue, $actualValue);
}
public function testBegin() {
public function testBegin()
{
$expectedValue = '<article class="form-group field-activefieldtestmodel-attributename">';
$this->activeField->options['tag'] = 'article';
$actualValue = $this->activeField->begin();
@ -217,7 +219,6 @@ EOT;
$this->assertEquals($expectedValue, $this->activeField->parts['{label}']);
}
public function testError()
{
$expectedValue = '<label class="control-label" for="activefieldtestmodel-attributename">Attribute Name</label>';
@ -506,6 +507,21 @@ EOD;
$this->assertEqualsWithoutLE($expectedValue, trim($actualValue));
}
public function testWidget()
{
$this->activeField->widget(TestInputWidget::className());
$this->assertEquals('Render: ' . TestInputWidget::className(), $this->activeField->parts['{input}']);
$widget = TestInputWidget::$lastInstance;
$this->assertSame($this->activeField->model, $widget->model);
$this->assertEquals($this->activeField->attribute, $widget->attribute);
$this->assertSame($this->activeField->form->view, $widget->view);
$this->assertSame($this->activeField, $widget->field);
$this->activeField->widget(TestInputWidget::className(), ['options' => ['id' => 'test-id']]);
$this->assertEquals('test-id', $this->activeField->labelOptions['for']);
}
/**
* Helper methods
*/
@ -572,3 +588,22 @@ class TestValidator extends \yii\validators\Validator
$this->whenClient = $js;
}
}
class TestInputWidget extends InputWidget
{
/**
* @var static
*/
public static $lastInstance;
public function init()
{
parent::init();
self::$lastInstance = $this;
}
public function run()
{
return 'Render: ' . get_class($this);
}
}
Loading…
Cancel
Save