diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7dce76c9f5c..ad632e4c7ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - php: [8.1, 8.2] + php: [8.1, 8.2, 8.3] steps: - name: Generate french locale. diff --git a/.github/workflows/ci-mssql.yml b/.github/workflows/ci-mssql.yml index 45e4d497275..74a46ade0ad 100644 --- a/.github/workflows/ci-mssql.yml +++ b/.github/workflows/ci-mssql.yml @@ -25,6 +25,7 @@ jobs: php: - 8.1 - 8.2 + - 8.3 mssql: - server:2017-latest diff --git a/.github/workflows/ci-mysql.yml b/.github/workflows/ci-mysql.yml index f882ab84e9e..034293fed70 100644 --- a/.github/workflows/ci-mysql.yml +++ b/.github/workflows/ci-mysql.yml @@ -24,6 +24,7 @@ jobs: php: - 8.1 - 8.2 + - 8.3 mysql: - 5.7 diff --git a/.github/workflows/ci-pgsql.yml b/.github/workflows/ci-pgsql.yml index f1ef701ebd0..7b9506007e1 100644 --- a/.github/workflows/ci-pgsql.yml +++ b/.github/workflows/ci-pgsql.yml @@ -24,6 +24,7 @@ jobs: php: - 8.1 - 8.2 + - 8.3 pgsql: - 10 diff --git a/.github/workflows/ci-sqlite.yml b/.github/workflows/ci-sqlite.yml index 80a61fe37b4..b7857e85249 100644 --- a/.github/workflows/ci-sqlite.yml +++ b/.github/workflows/ci-sqlite.yml @@ -25,6 +25,7 @@ jobs: php: - 8.1 - 8.2 + - 8.3 steps: - name: Checkout. diff --git a/composer.lock b/composer.lock index 84e3b662464..51c8363b94d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,14 +4,14 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5e0983ee92630b4d2c26d184a6ed3437", + "content-hash": "9ee35a67e578251573a9017059b62b76", "packages": [ { "name": "bower-asset/inputmask", "version": "5.0.8", "source": { "type": "git", - "url": "https://github.com/RobinHerbots/Inputmask.git", + "url": "git@github.com:RobinHerbots/Inputmask.git", "reference": "e0f39e0c93569c6b494c3a57edef2c59313a6b64" }, "dist": { @@ -29,16 +29,16 @@ }, { "name": "bower-asset/jquery", - "version": "3.7.1", + "version": "3.6.4", "source": { "type": "git", - "url": "https://github.com/jquery/jquery-dist.git", - "reference": "fde1f76e2799dd877c176abde0ec836553246991" + "url": "git@github.com:jquery/jquery-dist.git", + "reference": "91ef2d8836342875f2519b5815197ea0f23613cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/fde1f76e2799dd877c176abde0ec836553246991", - "reference": "fde1f76e2799dd877c176abde0ec836553246991" + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/91ef2d8836342875f2519b5815197ea0f23613cf", + "reference": "91ef2d8836342875f2519b5815197ea0f23613cf" }, "type": "bower-asset", "license": [ @@ -147,20 +147,20 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.16.0", + "version": "v4.17.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -202,9 +202,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" }, - "time": "2022-09-18T07:06:19+00:00" + "time": "2023-11-17T15:01:25+00:00" }, { "name": "yiisoft/yii2-composer", @@ -581,7 +581,54 @@ "issues": "https://github.com/rdohms/phpunit-arraysubset-asserts/issues", "source": "https://github.com/rdohms/phpunit-arraysubset-asserts/tree/v0.5.0" }, - "time": "2023-06-02T17:33:53+00:00" + "time": "2023-02-01T09:20:38+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" + }, + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/instantiator", @@ -2513,16 +2560,16 @@ }, { "name": "symfony/console", - "version": "v6.3.4", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6" + "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6", - "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6", + "url": "https://api.github.com/repos/symfony/console/zipball/0d14a9f6d04d4ac38a8cea1171f4554e325dae92", + "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92", "shasum": "" }, "require": { @@ -2583,7 +2630,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.4" + "source": "https://github.com/symfony/console/tree/v6.3.8" }, "funding": [ { @@ -2599,11 +2646,11 @@ "type": "tidelift" } ], - "time": "2023-08-16T10:10:12+00:00" + "time": "2023-10-31T08:09:35+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -2650,7 +2697,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -2750,7 +2797,7 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", @@ -2806,7 +2853,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -3573,16 +3620,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", "shasum": "" }, "require": { @@ -3635,7 +3682,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" }, "funding": [ { @@ -3651,7 +3698,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/stopwatch", @@ -3717,16 +3764,16 @@ }, { "name": "symfony/string", - "version": "v6.3.5", + "version": "v6.3.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339" + "reference": "13880a87790c76ef994c91e87efb96134522577a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339", - "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339", + "url": "https://api.github.com/repos/symfony/string/zipball/13880a87790c76ef994c91e87efb96134522577a", + "reference": "13880a87790c76ef994c91e87efb96134522577a", "shasum": "" }, "require": { @@ -3783,7 +3830,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.5" + "source": "https://github.com/symfony/string/tree/v6.3.8" }, "funding": [ { @@ -3799,7 +3846,7 @@ "type": "tidelift" } ], - "time": "2023-09-18T10:38:32+00:00" + "time": "2023-11-09T08:28:21+00:00" }, { "name": "theseer/tokenizer", diff --git a/docs/guide-ja/README.md b/docs/guide-ja/README.md index b86e3aaad98..c2c7997c695 100644 --- a/docs/guide-ja/README.md +++ b/docs/guide-ja/README.md @@ -135,6 +135,7 @@ RESTful ウェブ・サービス * [クイック・スタート](rest-quick-start.md) * [リソース](rest-resources.md) +* [コレクションのフィルタリング](rest-filtering-collections.md) * [コントローラ](rest-controllers.md) * [ルーティング](rest-routing.md) * [レスポンス形式の設定](rest-response-formatting.md) diff --git a/docs/guide-ja/caching-data.md b/docs/guide-ja/caching-data.md index 7195f3512f0..00608e000f7 100644 --- a/docs/guide-ja/caching-data.md +++ b/docs/guide-ja/caching-data.md @@ -57,7 +57,7 @@ $data = $cache->getOrSet($key, function () use ($user_id) { キャッシュ・コンポーネントは通常グローバルに設定しアクセスできるように [アプリケーション・コンポーネント](structure-application-components.md) として登録されます。 -以下のコードは、二台のキャッシュ・サーバを用いる [Memcached](https://memcached.org/) を使うように +以下のコードは、二台のキャッシュ・サーバを用いる [memcached](https://memcached.org/) を使うように `cache` アプリケーション・コンポーネントを構成する方法を示すものです。 ```php diff --git a/docs/guide-ja/caching-fragment.md b/docs/guide-ja/caching-fragment.md index 4648769f0aa..2e3bd038ab4 100644 --- a/docs/guide-ja/caching-fragment.md +++ b/docs/guide-ja/caching-fragment.md @@ -24,6 +24,11 @@ if ($this->beginCache($id)) { [データ・キャッシュ](caching-data.md) と同様に、キャッシュされるコンテントを識別するためにユニークな `$id` が必要になります。 +次のようにすると、フラグメント・キャッシュを削除することが出来ます。 +```php +Yii::$app->cache->delete(['yii\widgets\FragmentCache', $id]); +``` + ## キャッシュのオプション diff --git a/docs/guide-ja/concept-behaviors.md b/docs/guide-ja/concept-behaviors.md index a8583d92387..4389d7637ad 100644 --- a/docs/guide-ja/concept-behaviors.md +++ b/docs/guide-ja/concept-behaviors.md @@ -1,8 +1,8 @@ ビヘイビア ========== -ビヘイビアは [[yii\base\Behavior]] またその子クラスのインスタンスです。ビヘイビアは -[ミックスイン](https://ja.wikipedia.org/wiki/Mixin) としても知られ、既存の [[yii\base\Component|component]] クラスの +ビヘイビアは [[yii\base\Behavior]] またその子クラスのインスタンスです。 +ビヘイビアは [ミックスイン](https://ja.wikipedia.org/wiki/Mixin) としても知られ、既存の [[yii\base\Component|component]] クラスの 機能を、クラスの継承を変更せずに拡張することができます。コンポーネントにビヘイビアをアタッチすると、その コンポーネントにはビヘイビアのメソッドとプロパティが "注入" され、それらのメソッドとプロパティは、 コンポーネント・クラス自体に定義されているかのようにアクセスできるようになります。また、ビヘイビアは、 diff --git a/docs/guide-ja/concept-configurations.md b/docs/guide-ja/concept-configurations.md index b62113647ad..6c664ea97a7 100644 --- a/docs/guide-ja/concept-configurations.md +++ b/docs/guide-ja/concept-configurations.md @@ -103,7 +103,7 @@ $config = [ 'class' => 'yii\caching\FileCache', ], 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', + 'class' => 'yii\symfonymailer\Mailer', ], 'log' => [ 'class' => 'yii\log\Dispatcher', @@ -207,7 +207,7 @@ return [ 'class' => 'yii\caching\FileCache', ], 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', + 'class' => 'yii\symfonymailer\Mailer', ], 'log' => [ 'class' => 'yii\log\Dispatcher', diff --git a/docs/guide-ja/concept-di-container.md b/docs/guide-ja/concept-di-container.md index 9580d425118..c603b5cf615 100644 --- a/docs/guide-ja/concept-di-container.md +++ b/docs/guide-ja/concept-di-container.md @@ -541,7 +541,7 @@ $reader = $container->get('app\storage\DocumentsReader'); 依存注入と [サービス・ロケータ](concept-service-locator.md) はともに、疎結合でよりテストしやすい方法でのソフトウェア構築を可能にする、 定番のデザインパターンです。 -依存注入とサービス・ロケータへのより深い理解を得るために、 [Martin の記事](https://martinfowler.com/articles/injection.html) +依存注入とサービス・ロケータをより深く理解するために、 [Martin の記事](https://martinfowler.com/articles/injection.html) を読むことを強くお勧めします。 Yii はその [サービス・ロケータ](concept-service-locator.md) を、依存注入 (DI) コンテナの上に実装しています。 diff --git a/docs/guide-ja/concept-properties.md b/docs/guide-ja/concept-properties.md index 7692298683f..14464795ee7 100644 --- a/docs/guide-ja/concept-properties.md +++ b/docs/guide-ja/concept-properties.md @@ -80,7 +80,7 @@ getter と setter で定義されたプロパティには、いくつかの特 * 通常の `property_exists()` の呼び出しでは、マジック・プロパティが存在するかどうかを知ることは出来ません。 それぞれ、[[yii\base\BaseObject::canGetProperty()|canGetProperty()]] または [[yii\base\BaseObject::canSetProperty()|canSetProperty()]] を呼び出さなければなりません。 -このガイドの冒頭で説明した問題に戻ると、 `label` に値が代入されているあらゆる箇所で `trim()` を呼ぶのではなく、 -もう `setLabel()` という setter の内部だけで `trim()` を呼べば済むのです。 +このガイドの冒頭で説明した問題に戻ると、`label` に値が代入されているあらゆる箇所で `trim()` を呼ぶのではなく、 +`setLabel()` という setter の内部だけで `trim()` を呼べば済むようになります。 さらに、新しい要求でラベルの先頭を大文字にする必要が発生しても、他のいっさいのコードに触れることなく、 すぐに `setLabel()` メソッドを変更することができます。一箇所の変更は、すべての `label` への代入に普遍的に作用します。 diff --git a/docs/guide-ja/concept-service-locator.md b/docs/guide-ja/concept-service-locator.md index e3082432de0..7cf325dac34 100644 --- a/docs/guide-ja/concept-service-locator.md +++ b/docs/guide-ja/concept-service-locator.md @@ -129,4 +129,4 @@ return [ モジュールからサービスを引き出そうとする全てのリクエストは、そのモジュールが要求に応じられない場合は、すべてその親に渡されます。 モジュール内のコンポーネントの構成情報は、親モジュール内のコンポーネントの構成情報とは決してマージされないことに注意して下さい。 -サービス・ロケータ・パターンによって私たちは名前の付いたサービスを定義することが出来ますが、同じ名前のサービスが同じ構成パラメータを使用すると想定することは出来ません。 +サービス・ロケータのパターンによって私たちは名前の付いたサービスを定義することが出来ますが、同じ名前のサービスが同じ構成パラメータを使用すると想定することは出来ません。 diff --git a/docs/guide-ja/db-dao.md b/docs/guide-ja/db-dao.md index f5198033806..fa8d5debafd 100644 --- a/docs/guide-ja/db-dao.md +++ b/docs/guide-ja/db-dao.md @@ -680,5 +680,5 @@ $table = Yii::$app->db->getTableSchema('post'); ``` このメソッドは、テーブルのカラム、プライマリ・キー、外部キーなどの情報を含む [[yii\db\TableSchema]] オブジェクトを返します。 -これらの情報は、主として [クエリ・ビルダ](db-query-builder.md) や [アクティブ・レコード](db-active-record.md) によって利用されて、 +この情報は、主として [クエリ・ビルダ](db-query-builder.md) や [アクティブ・レコード](db-active-record.md) によって利用されて、 特定のデータベースに依存しないコードを書くことを助けてくれています。 diff --git a/docs/guide-ja/db-migrations.md b/docs/guide-ja/db-migrations.md index a0b489cb0cd..6fa39cfae34 100644 --- a/docs/guide-ja/db-migrations.md +++ b/docs/guide-ja/db-migrations.md @@ -685,7 +685,7 @@ class m150101_185401_create_news_table extends Migration `safeDown()` では、先に行を削除して、次にテーブルを削除しています。 > Note: 全ての DBMS がトランザクションをサポートしている訳ではありません。また、トランザクションに入れることが出来ない DB クエリもあります。 - いくつかの例を [暗黙のコミット](https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html) で見ることが出来ます。 + そのいくつかの例を [暗黙のコミット](https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html) で見ることが出来ます。 その場合には、代りに、`up()` と `down()` を実装しなければなりません。 diff --git a/docs/guide-ja/db-query-builder.md b/docs/guide-ja/db-query-builder.md index 996b2a542f9..170e1aa2c89 100644 --- a/docs/guide-ja/db-query-builder.md +++ b/docs/guide-ja/db-query-builder.md @@ -299,7 +299,7 @@ $query->where([$column => $value]); 値が自動的に一対のパーセント記号によって囲まれることに注意してください。 > Note: PostgreSQL を使っている場合は、`like` の代りに、大文字と小文字を区別しない比較のための - > [`ilike`](https://www.postgresql.org/docs/8.3/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。 + > [`ilike`](https://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE) を使うことも出来ます。 - `or like`: オペランド 2 が配列である場合に `LIKE` 述語が `OR` によって結合される以外は、 `like` 演算子と同じです。 diff --git a/docs/guide-ja/input-tabular-input.md b/docs/guide-ja/input-tabular-input.md index 252ca07a14b..48f2f7e986e 100644 --- a/docs/guide-ja/input-tabular-input.md +++ b/docs/guide-ja/input-tabular-input.md @@ -43,12 +43,14 @@ class SettingsController extends Controller { $settings = Setting::find()->indexBy('id')->all(); - if (Model::loadMultiple($settings, Yii::$app->request->post()) && Model::validateMultiple($settings)) { + if ($this->request->isPost) { + if (Setting::loadMultiple($settings, $this->request->post()) && Setting::validateMultiple($settings)) { foreach ($settings as $setting) { $setting->save(false); } return $this->redirect('index'); } + } return $this->render('update', ['settings' => $settings]); } @@ -71,10 +73,12 @@ use yii\widgets\ActiveForm; $form = ActiveForm::begin(); -foreach ($settings as $index => $setting) { - echo $form->field($setting, "[$index]value")->label($setting->name); +foreach ($settings as $id => $setting) { + echo $form->field($setting, "[$id]value")->label($setting->name); } +echo Html::submitButton('Save'); + ActiveForm::end(); ``` @@ -88,20 +92,29 @@ ActiveForm::end(); ```php public function actionCreate() { - $count = count(Yii::$app->request->post('Setting', [])); - $settings = [new Setting()]; - for($i = 1; $i < $count; $i++) { - $settings[] = new Setting(); + $settings = []; + if ($this->request->isPost) { + $count = count($this->request->post($setting->tableName())) - 1; + for ($i = 0; $i < $count; $i++) { + $settings[$i] = new Setting(); + } + if (Setting::loadMultiple($settings, $this->request->post()) && Setting::validateMultiple($settings)) { + foreach ($settings as $setting) { + $setting->save(false); + } + return $this->redirect('index'); } + } + $settings[] = new Setting(); - // ... + return $this->render('create', ['settings' => $settings]); } ``` ここでは、デフォルトで一個のモデルを含む `$settings` 配列を初期値として作成し、少なくとも一個のテキスト・フィールドが常にビューに表示されるようにしています。 そして、受信したインプットの行数に合せて、配列にモデルを追加しています。 -ビューでは javascript を使ってインプットの行を動的に追加することが出来ます。 +ビューでは JavaScript を使ってインプットの行を動的に追加することが出来ます。 ### 更新、作成、削除を一つのページに組み合わせる diff --git a/docs/guide-ja/input-validation.md b/docs/guide-ja/input-validation.md index 420317c0f35..b8826135377 100644 --- a/docs/guide-ja/input-validation.md +++ b/docs/guide-ja/input-validation.md @@ -748,7 +748,7 @@ JS; ### Deferred 検証 -非同期のクライアント・サイドの検証をサポートする必要がある場合は、[Defered オブジェクト](https://api.jquery.com/category/deferred-object/) を作成することが出来ます。 +非同期のクライアント・サイドの検証をサポートする必要がある場合は、[Deferred オブジェクト](https://api.jquery.com/category/deferred-object/) を作成することが出来ます。 例えば、AJAX によるカスタム検証を実行するために、次のコードを使うことが出来ます。 ```php diff --git a/docs/guide-ja/intro-upgrade-from-v1.md b/docs/guide-ja/intro-upgrade-from-v1.md index 53010f0da0e..b6dc75585d8 100644 --- a/docs/guide-ja/intro-upgrade-from-v1.md +++ b/docs/guide-ja/intro-upgrade-from-v1.md @@ -265,7 +265,7 @@ ActiveForm::end(); テーマは、ソースのビュー・ファイル・パスをテーマのビュー・ファイル・パスにマップするパス・マッピング機構に基づくものになりました。 例えば、あるテーマのパス・マップが `['/web/views' => '/web/themes/basic']` である場合、ビュー・ファイル `/web/views/site/index.php` のテーマ版は `/web/themes/basic/site/index.php` になります。 この理由により、テーマはどのようなビュー・ファイルに対してでも適用することが出来るようになりました。 -コントローラやウィジェットのコンテキストの外で表示されるビューに対してすら、適用できます。 +コントローラやウィジェットのコンテキストの外で表示されるビューであっても適用できます。 また、`CThemeManager` コンポーネントはもうありません。 その代りに、`theme` は `view` アプリケーション・コンポーネントの構成可能なプロパティになりました。 diff --git a/docs/guide-ja/output-client-scripts.md b/docs/guide-ja/output-client-scripts.md index 38ac328e2a4..2e307ab69ff 100644 --- a/docs/guide-ja/output-client-scripts.md +++ b/docs/guide-ja/output-client-scripts.md @@ -40,8 +40,8 @@ $this->registerJs( - [[yii\web\View::POS_LOAD|View::POS_LOAD]] - [ドキュメントの `load` イベント](https://learn.jquery.com/using-jquery-core/document-ready/) でコードを実行するための指定。 上記と同じく、これを指定すると、[[yii\web\JqueryAsset|jQuery]] が自動的に登録されます。 -最後の引数は、スクリプトのコード・ブロックを一意に特定するために使われるスクリプトのユニークな ID です。同じ ID のスクリプトが既にある場合は、新しいものを追加するのでなく、 -それを置き換えます。ID を指定しない場合は、JS コードそれ自身が ID として扱われます。この ID によって、同じコードが複数回登録されるのを防止します。 +最後の引数は、スクリプトのコード・ブロックを一意に特定するために使われるスクリプトのユニークな ID です。同じ ID のスクリプトが既にある場合は、新しいものを追加するのでなく、それを置き換えます。 +ID を指定しない場合は、JS コードそれ自身が ID として扱われます。この ID によって、同じコードが複数回登録されるのを防止します。 ### スクリプト・ファイルを登録する diff --git a/docs/guide-ja/output-data-providers.md b/docs/guide-ja/output-data-providers.md index d15d313f457..0344016325d 100644 --- a/docs/guide-ja/output-data-providers.md +++ b/docs/guide-ja/output-data-providers.md @@ -354,13 +354,13 @@ $filter = new ActiveDataFilter([ $filterCondition = null; -// どのようなソースからでもフィルタをロードすることが出来ます。 -// 例えば、リクエスト・ボディの JSON からロードしたい場合は、 +// どのようなソースからでもフィルタをロードすることが出来ます。例えば、 +// リクエスト・ボディの JSON からロードしたい場合は、 // 下記のように Yii::$app->request->getBodyParams() を使います。 if ($filter->load(\Yii::$app->request->get())) { $filterCondition = $filter->build(); if ($filterCondition === false) { - // シリアライザがエラーを抽出するだろう + // シリアライザがフィルタの抽出でエラーを出すかもしれない return $filter; } } diff --git a/docs/guide-ja/output-data-widgets.md b/docs/guide-ja/output-data-widgets.md index 28946fe3393..b4dd7d4a05c 100644 --- a/docs/guide-ja/output-data-widgets.md +++ b/docs/guide-ja/output-data-widgets.md @@ -777,4 +777,4 @@ yii gii/crud --controllerClass="backend\\controllers\PostController" \ さらに読むべき文書 ------------------ -- Arno Slatius による [Rendering Data in Yii 2 with GridView and ListView](https://www.sitepoint.com/rendering-data-in-yii-2-with-gridview-and-listview/)。 +- Arno Slatius による [Rendering Data in Yii 2 with GridView and ListView](https://www.sitepoint.com/rendering-data-in-yii-2-with-gridview-and-listview/) diff --git a/docs/guide-ja/output-formatting.md b/docs/guide-ja/output-formatting.md index bcfaabc9d6c..4faf5c3c3ac 100644 --- a/docs/guide-ja/output-formatting.md +++ b/docs/guide-ja/output-formatting.md @@ -75,7 +75,7 @@ return [ - [[yii\i18n\Formatter::asDate()|date]] - 値は日付としてフォーマットされます。例えば `January 01, 2014`。 - [[yii\i18n\Formatter::asTime()|time]] - 値は時刻としてフォーマットされます。例えば `14:23`。 - [[yii\i18n\Formatter::asDatetime()|datetime]] - 値は日付および時刻としてフォーマットされます。例えば `January 01, 2014 14:23`。 -- [[yii\i18n\Formatter::asTimestamp()|timestamp]] - 値は [unix タイムスタンプ](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93) としてフォーマットされます。例えば `1412609982`。 +- [[yii\i18n\Formatter::asTimestamp()|timestamp]] - 値は [unix タイムスタンプ](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93) としてフォーマットされます。例えば `1412609982` - [[yii\i18n\Formatter::asRelativeTime()|relativeTime]] - 値は、その日時と現在との間隔として、人間に分かりやすい言葉でフォーマットされます。 例えば `1 hour ago`。 - [[yii\i18n\Formatter::asDuration()|duration]] - 値は継続時間として、人間に分かりやすい言葉でフォーマットされます。例えば `1 day, 2 minutes`。 diff --git a/docs/guide-ja/output-sorting.md b/docs/guide-ja/output-sorting.md index 945c331a0d2..ec18e82c952 100644 --- a/docs/guide-ja/output-sorting.md +++ b/docs/guide-ja/output-sorting.md @@ -65,7 +65,7 @@ $articles = Article::find() ラベルは HTML エンコードされないことに注意してください。 > Info: [[yii\data\Sort::$orders|orders]] の値をデータベースのクエリに直接に供給して、 - `ORDER BY` 句を構築することが出来ます。 データベースのクエリが認識できない合成的な属性が入っている場合があるため、 + `ORDER BY` 句を構築することが出来ます。データベースのクエリが認識できない合成的な属性が入っている場合があるため、 [[yii\data\Sort::$attributeOrders|attributeOrders]] を使ってはいけません。 [[yii\data\Sort::link()]] を呼んでハイパーリンクを生成すれば、それをクリックして、指定した属性によるデータの並べ替えをリクエストすることが出来るようになります。 diff --git a/docs/guide-ja/rest-authentication.md b/docs/guide-ja/rest-authentication.md index a811526f1a5..6a236cea835 100644 --- a/docs/guide-ja/rest-authentication.md +++ b/docs/guide-ja/rest-authentication.md @@ -12,7 +12,7 @@ * [HTTP Basic 認証](https://ja.wikipedia.org/wiki/Basic%E8%AA%8D%E8%A8%BC): アクセス・トークンはユーザ名として送信されます。 この方法は、アクセス・トークンを API コンシューマ側で安全に保存することが出来る場合、 - 例えば API コンシューマがサーバ上で走るプログラムである場合などにのみ使用されるべきです。 + 例えば API コンシューマがサーバ上で走るプログラムである場合などのみに使用されるべきです。 * クエリ・パラメータ: アクセス・トークンは API の URL、例えば、`https://example.com/users?access-token=xxxxxxxx` でクエリ・パラメータとして送信されます。 ほとんどのウェブ・サーバはクエリ・パラメータをサーバのログに記録するため、この手法は、 diff --git a/docs/guide-ja/rest-filtering-collections.md b/docs/guide-ja/rest-filtering-collections.md new file mode 100644 index 00000000000..7eb809679e5 --- /dev/null +++ b/docs/guide-ja/rest-filtering-collections.md @@ -0,0 +1,190 @@ +コレクションのフィルタリング +============================ + +バージョン 2.0.13 以降、リソースのコレクションは [[yii\data\DataFilter]] コンポーネントを使ってフィルタにかけることが出来ます。 +このコンポーネントは、リクエスト経由で渡されるフィルタ条件の構築を可能にし、そして、拡張バージョンの [[yii\data\ActiveDataFilter]] の助力によって、 +[[yii\db\QueryInterface::where()]] にとって適切な形式でフィルタ条件を使う事を可能にします。 + + +## データ・プロバイダをフィルタリングのために構成する + +[コレクション](rest-resources.md#collections) のセクションで言及されているように、 +[データ・プロバイダ](output-data-providers#data-providers) を使うと、並べ替えてページ付けしたリソースのリストを出力することが出来ます。 +また、データ・プロバイダを使って、そのリストをフィルタにかけることも出来ます。 + +```php +$filter = new ActiveDataFilter([ + 'searchModel' => 'app\models\PostSearch', +]); + +$filterCondition = null; +// どのようなソースからでもフィルタをロードすることが出来ます。例えば、 +// リクエスト・ボディの JSON からロードしたい場合は、 +// 下記のように Yii::$app->request->getBodyParams() を使います。 +if ($filter->load(Yii::$app->request->get())) { + $filterCondition = $filter->build(); + if ($filterCondition === false) { + // シリアライザがフィルタの抽出でエラーを出すかもしれない + return $filter; + } +} + +$query = Post::find(); +if ($filterCondition !== null) { + $query->andWhere($filterCondition); +} + +return new ActiveDataProvider([ + 'query' => $query, +]); +``` + +`PostSearch` モデルが、どのプロパティと値がフィルタリングのために許容されるかを定義する役目を担います。 + +```php +use yii\base\Model; + +class PostSearch extends Model +{ + public $id; + public $title; + + public function rules() + { + return [ + ['id', 'integer'], + ['title', 'string', 'min' => 2, 'max' => 200], + ]; + } +} +``` + +そこで特別なビジネス・ロジックが必要でない場合には、検索ルールのためのスタンドアロンなモデルを準備する代わりに、 +[[yii\base\DynamicModel]] を使うことが出来ます。 + +```php +$filter = new ActiveDataFilter([ + 'searchModel' => (new DynamicModel(['id', 'title'])) + ->addRule(['id'], 'integer') + ->addRule(['title'], 'string', ['min' => 2, 'max' => 200]), +]); +``` + +`searchModel` を定義することは、エンド・ユーザに許容するフィルタ条件を制御するために欠かすことが出来ません。 + + +## リクエストのフィルタリング + +通常、エンド・ユーザは許容された一つ以上のメソッド(これらはAPIドキュメントに明示的に記述されるべきものです)を使ってフィルタリング条件をリクエストで提供するものと期待されます。 +例えば、フィルタリングが JSON を使って POST メソッドで操作される場合は、 +下記と似たようなものになります。 + +```json +{ + "filter": { + "id": {"in": [2, 5, 9]}, + "title": {"like": "cheese"} + } +} +``` + +上記の条件は、次のように解釈されます : +- `id` は、2, 5, または 9 でなければならず、**かつD** +- `title` は `cheese` という語を含まなければならない。 + +同一の条件が GET クエリの一部として送信される場合は、次のようになります : + +``` +?filter[id][in][]=2&filter[id][in][]=5&filter[id][in][]=9&filter[title][like]=cheese +``` + +デフォルトの `filter` キー・ワードは、[[yii\data\DataFilter::$filterAttributeName]] を設定して変更することが出来ます。 + + +## フィルタ制御キーワード + +許容されているフィルタ制御キーワードは下記の通りです : + +| キーワード | 意味 | +|:--------------:|:-------------:| +| `and` | `AND` | +| `or` | `OR` | +| `not` | `NOT` | +| `lt` | `<` | +| `gt` | `>` | +| `lte` | `<=` | +| `gte` | `>=` | +| `eq` | `=` | +| `neq` | `!=` | +| `in` | `IN` | +| `nin` | `NOT IN` | +| `like` | `LIKE` | + +オプションの [[yii\data\DataFilter::$filterControls]] を拡張して、上記のリストを拡張することが出来ます。 +例えば、下記のように、同一のフィルタ構築キーにいくつかのキーワードを与えて、複数のエイリアスを作成することが出来ます : + +```php +[ + 'eq' => '=', + '=' => '=', + '==' => '=', + '===' => '=', + // ... +] +``` + +未定義のキーワードは、すべて、フィルタ制御とは認識されず、属性名として扱われることに注意して下さい。 +制御キーワードと属性名の衝突は避けなければなりません。 +(例えば、制御キーワードとしての 'like' と属性名としての 'like' が存在する場合、そのような属性に対して条件を指定することは不可能です。) + +> Note: フィルタ制御を指定する時に、あなたのAPIが使用する実際のデータ交換形式に留意しましょう。 + すべての指定された制御キーワードがその形式にとって妥当なものであることを確認して下さい。 + 例えば、XML ではタグ名は Letter クラスの文字でしか開始出来ませんから、`>`, `=`, `$gt` 等は XML スキーマに違反することになります。 + +> Note: 新しいフィルタ制御キーワードを追加する時は、演算子の結合規則および所期の動作に基づいて、期待されるクエリ結果を得るためには + [[yii\data\DataFilter::$conditionValidators]] および/または [[yii\data\DataFilter::$operatorTypes]] をも + 更新する必要があるかどうか、必ず確認して下さい。 + + +## Null 値の扱い + +JSON の式野中では `null` を使う事は容易ですが、文字通りの 'null' を文字列としての "null" と混乱させずに GET クエリを使ってを送信することは不可能です。 +バージョン 2.0.40 以降では、[[yii\data\DataFilter::$nullValue]] オプションを使って、文字通りの `null` に置換される単語(デフォルトでは、"NULL")を構成することが出来ます。 + + +## 属性のエイリアス + +属性を別の名前で呼びたい場合や、結合された DB テーブルでフィルタをかけたい場合に、 +[[yii\data\DataFilter::$attributeMap]] を使ってエイリアスのマップを設定することが出来ます。 + +```php +[ + 'carPart' => 'car_part', // car_part 属性でフィルタするために carPart が使われる + 'authorName' => '{{author}}.[[name]]', // 結合された author テーブルの name 属性でフィルタするために authorName が使われる +] +``` + +## `ActiveController` のためにフィルタを構成する + +[[yii\rest\ActiveController]] には一般的な一揃いの REST アクションが失踪されていますが、 +[[yii\rest\IndexAction::$dataFilter]] プロパティによってフィルタを使うことも簡単に出来ます。 +可能な方法のうちの一つは [[yii\rest\ActiveController::actions()]] を使ってそうすることです : + +```php +public function actions() +{ + $actions = parent::actions(); + + $actions['index']['dataFilter'] = [ + 'class' => \yii\data\ActiveDataFilter::class, + 'attributeMap' => [ + 'clockIn' => 'clock_in', + ], + 'searchModel' => (new DynamicModel(['id', 'clockIn']))->addRule(['id', 'clockIn'], 'integer', ['min' => 1]), + ]; + + return $actions; +} +``` + +これで(`index` アクションによってアクセス可能な)コレクションを `id` と `clockIn` プロパティによってフィルタすることが出来ます。 diff --git a/docs/guide-ja/rest-resources.md b/docs/guide-ja/rest-resources.md index 09020bca275..07f71703825 100644 --- a/docs/guide-ja/rest-resources.md +++ b/docs/guide-ja/rest-resources.md @@ -251,11 +251,3 @@ class PostController extends Controller REST API におけるコレクションはデータ・プロバイダであるため、データ・プロバイダの全ての機能、すなわち、ページネーションやソーティングを共有しています。 その一例を [クイック・スタート](rest-quick-start.md#trying-it-out) のセクションで見ることが出来ます。 - -### コレクションをフィルタリングする - -バージョン 2.0.13 以降、Yii はコレクションをフィルタリングする便利な機能を提供しています。 -その一例を [クイック・スタート](rest-quick-start.md#trying-it-out) のガイドに見ることが出来ます。 -エンド・ボイントをあなた自身が実装しようとしている場合、フィルタリングは -データ・プロバイダのガイドの [データ・フィルタを使ってデータ・プロバイダをフィルタリングする](output-data-providers.md#filtering-data-providers-using-data-filters - のセクションで述べられている方法で行うことが出来ます。 diff --git a/docs/guide-ja/rest-response-formatting.md b/docs/guide-ja/rest-response-formatting.md index f30433f0170..502d5707a08 100644 --- a/docs/guide-ja/rest-response-formatting.md +++ b/docs/guide-ja/rest-response-formatting.md @@ -3,7 +3,7 @@ RESTful API のリクエストを処理するとき、アプリケーションは、通常、レスポンス形式の設定に関して次のステップを踏みます。 -1. レスポンス形式に影響するさまざまな要因、例えば、メディア・タイプ、言語、バージョンなどを決定します。 +1. レスポンス形式に影響しうるさまざまな要因、例えば、メディア・タイプ、言語、バージョンなどを決定します。 このプロセスは [コンテント・ネゴシエーション](https://en.wikipedia.org/wiki/Content_negotiation) としても知られるものです。 2. リソース・オブジェクトを配列に変換します。 [リソース](rest-resources.md) のセクションで説明したように、この作業は [[yii\rest\Serializer]] によって実行されます。 diff --git a/docs/guide-ja/rest-versioning.md b/docs/guide-ja/rest-versioning.md index f9b73f9006c..6b89c8e07ae 100644 --- a/docs/guide-ja/rest-versioning.md +++ b/docs/guide-ja/rest-versioning.md @@ -5,7 +5,7 @@ クライアント・サイドとサーバ・サイドの両方のコードを完全に制御できるウェブ・アプリケーションとは違って、API はあなたの制御が及ばないクライアントによって使用されることを想定したものです。このため、API の後方互換性 (BC) は、可能な限り保たれなければなりません。 BC を損なうかも知れない変更が必要な場合は、それを API の新しいバージョンにおいて導入し、バージョン番号を上げるべきです。そうすれば、既存のクライアントは、API の古いけれども動作するバージョンを使い続けることが出来ますし、新しいまたはアップグレードされたクライアントは、新しい API バージョンで新しい機能を使うことが出来ます。 -> Tip: API のバージョン番号の設計に関する詳細情報は +> Tip: API のバージョン番号の設計についての詳細な情報は [Semantic Versioning](https://semver.org/) を参照してください。 API のバージョン管理を実装する方法としてよく使われるのは、バージョン番号を API の URL に埋め込む方法です。 diff --git a/docs/guide-ja/runtime-requests.md b/docs/guide-ja/runtime-requests.md index 94257ad13a6..fde78a39a99 100644 --- a/docs/guide-ja/runtime-requests.md +++ b/docs/guide-ja/runtime-requests.md @@ -152,8 +152,9 @@ Yii アプリケーションに渡されるからです。 信頼できるプロキシの情報を構成することが出来るようになっています。 [[yii\web\Request::trustedHosts|trustedHosts]]、 [[yii\web\Request::secureHeaders|secureHeaders]]、 -[[yii\web\Request::ipHeaders|ipHeaders]] および -[[yii\web\Request::secureProtocolHeaders|secureProtocolHeaders]] +[[yii\web\Request::ipHeaders|ipHeaders]] +[[yii\web\Request::secureProtocolHeaders|secureProtocolHeaders]] および +[[yii\web\Request::portHeaders|portHeaders]] (2.0.46 以降) 以下は、リバース・プロキシ・アレイの背後で動作するアプリケーションのための、request の構成例です (リバース・プロキシ・アレイは `10.0.2.0/24` のネットワークに設置されているとします)。 diff --git a/docs/guide-ja/runtime-sessions-cookies.md b/docs/guide-ja/runtime-sessions-cookies.md index 92eefef8b51..ae09ec64a5c 100644 --- a/docs/guide-ja/runtime-sessions-cookies.md +++ b/docs/guide-ja/runtime-sessions-cookies.md @@ -389,7 +389,8 @@ Yii 2.0.21 以降、[[yii\web\Cookie::sameSite]] 設定がサポートされて ブラウザが `sameSite` 設定をサポートしている場合、指定されたポリシー ('Lax' または 'Strict') に従うクッキーだけが送信されます。 詳細については [SameSite の wiki 記事](https://owasp.org/www-community/SameSite) を参照して下さい。 更なるセキュリティ強化のために、`sameSite` がサポートされていない PHP のバージョンで使われた場合には例外が投げられます。 -この機能を PHP のバージョンに関わりなく使用する場合は、最初にバージョンをチェックして下さい。例えば、 +この機能を PHP のバージョンに関わりなく使用する場合は、最初にバージョンをチェックして下さい。例えば + ```php [ 'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null, diff --git a/docs/guide-ja/security-authorization.md b/docs/guide-ja/security-authorization.md index 6375122461f..8b22f1591a8 100644 --- a/docs/guide-ja/security-authorization.md +++ b/docs/guide-ja/security-authorization.md @@ -157,7 +157,7 @@ class SiteController extends Controller ロール・ベース・アクセス制御 (RBAC) は、単純でありながら強力な集中型のアクセス制御を提供します。 RBAC と他のもっと伝統的なアクセス制御スキーマとの比較に関する詳細については、 -[Wiki 記事](https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%99%E3%83%BC%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1) を参照してください。 +[Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%99%E3%83%BC%E3%82%B9%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1) を参照してください。 Yii は、[NIST RBAC モデル](https://csrc.nist.gov/CSRC/media/Publications/conference-paper/1992/10/13/role-based-access-controls/documents/ferraiolo-kuhn-92.pdf) に従って、一般的階層型 RBAC を実装しています。 RBAC の機能は、[[yii\rbac\ManagerInterface|authManager]] [アプリケーション・コンポーネント](structure-application-components.md) を通じて提供されます。 diff --git a/docs/guide-ja/security-best-practices.md b/docs/guide-ja/security-best-practices.md index 5e39114c63f..d38e18faab9 100644 --- a/docs/guide-ja/security-best-practices.md +++ b/docs/guide-ja/security-best-practices.md @@ -333,8 +333,8 @@ H5BP プロジェクトが提供する構成例を参考にすることも出来 - Apache 2: - Nginx: -サーバの構成にアクセスする権限がない場合は、このような攻撃に対して防御するために、[[yii\filters\HostControl]] -フィルタを設定することが出来ます。 +サーバの構成にアクセスする権限がない場合は、このような攻撃に対して防御するために、 +[[yii\filters\HostControl]] フィルタを設定することが出来ます。 ```php // ウェブ・アプリケーション構成ファイル diff --git a/docs/guide-ja/start-databases.md b/docs/guide-ja/start-databases.md index b1393aaf545..933696b1757 100644 --- a/docs/guide-ja/start-databases.md +++ b/docs/guide-ja/start-databases.md @@ -106,8 +106,8 @@ class Country extends ActiveRecord } ``` -`Country` クラスは [[yii\db\ActiveRecord]] を拡張しています。この中には一つもコードを書く必要はありません。 -単に上記のコードだけで、Yii は関連付けられたテーブル名をクラス名から推測します。 +`Country` クラスは [[yii\db\ActiveRecord]] を拡張しています。ここにコードを追加する必要は全くありません。 +上記のコードだけで、Yii は関連付けられたテーブル名をクラス名から推測します。 > Info: クラス名とテーブル名を直接に合致させることが出来ない場合は、[[yii\db\ActiveRecord::tableName()]] メソッドをオーバーライドして、関連づけられたテーブル名を明示的に指定することが出来ます。 diff --git a/docs/guide-ja/start-prerequisites.md b/docs/guide-ja/start-prerequisites.md index 8aeac1fb64a..bce70b98d0c 100644 --- a/docs/guide-ja/start-prerequisites.md +++ b/docs/guide-ja/start-prerequisites.md @@ -5,7 +5,7 @@ Yii の学習曲線は他の PHP フレームワークほど急峻ではあり ## PHP Yii は PHP フレームワークですから、必ず [言語リファレンスを読んで理解する](https://www.php.net/manual/ja/langref.php) ようにして下さい。 -Yii を使って開発するときはオブジェクト指向の流儀でコードを書くことになりますから、必ず、[クラスとオブジェクト](https://www.php.net/manual/ja/language.oop5.basic.php) および [名前空間](https://www.php.net/manual/ja/language.namespaces.php) には慣れ親しんでおいて下さい。 +Yii を使って開発するときはオブジェクト指向の流儀でコードを書くことになりますから、必ず、[クラスとオブジェクト](https://www.php.net/manual/ja/language.oop5.basic.php) および [名前空間](https://www.php.net/manual/ja/language.namespaces.php) に慣れ親しんでおいて下さい。 ## オブジェクト指向プログラミング diff --git a/docs/guide-ja/structure-application-components.md b/docs/guide-ja/structure-application-components.md index a75554af1af..7575f172ca1 100644 --- a/docs/guide-ja/structure-application-components.md +++ b/docs/guide-ja/structure-application-components.md @@ -58,7 +58,7 @@ けれども、場合によっては、明示的にアクセスされないときでも、リクエストごとにアプリケーション・コンポーネントのインスタンスを作成したいことがあります。 そうするために、アプリケーションの [[yii\base\Application::bootstrap|bootstrap]] プロパティのリストにそのコンポーネントの ID を挙げることが出来ます。 -また、カスタマイズしたコンポーネントをブートストラップするためにクロージャを用いることも出来ます。インスタンス化されたコンポーネントを返すことは要求されません。 +また、カスタマイズされたコンポーネントをブートストラップするためにクロージャを用いることも出来ます。インスタンス化されたコンポーネントを返すことは要求されません。 単に [[yii\base\Application]] のインスタンス化の後にコードを走らせるだけのためにクロージャを使うことも出来ます。 例えば、次のアプリケーション構成情報は、`log` コンポーネントが常にロードされることを保証するものです。 diff --git a/docs/guide-ja/structure-applications.md b/docs/guide-ja/structure-applications.md index 5ec671f5eb7..c3c44b8ba9f 100644 --- a/docs/guide-ja/structure-applications.md +++ b/docs/guide-ja/structure-applications.md @@ -276,7 +276,7 @@ if (YII_ENV_DEV) { このプロパティを使用して決定します。 言語を指定するのには、[IETF 言語タグ](https://ja.wikipedia.org/wiki/IETF%E8%A8%80%E8%AA%9E%E3%82%BF%E3%82%B0) に従うことが推奨されます。 -例えば、`en` は英語を意味し、`en-US` はアメリカ合衆国の英語を意味します。 +例えば、`en` は英語を意味しますが、`en-US` はアメリカ合衆国の英語を意味します。 このプロパティに関する詳細は [国際化](tutorial-i18n.md) のセクションで読むことが出来ます。 @@ -348,7 +348,7 @@ $width = \Yii::$app->params['thumbnail.size'][0]; [language](#language) プロパティと同様に、このプロパティは [IETF 言語タグ](https://ja.wikipedia.org/wiki/IETF%E8%A8%80%E8%AA%9E%E3%82%BF%E3%82%B0) に従って構成しなければなりません。 -例えば、`en` は英語を意味し、`en-US` はアメリカ合衆国の英語を意味します。 +例えば、`en` は英語を意味しますが、`en-US` はアメリカ合衆国の英語を意味します。 このプロパティに関する詳細は [国際化](tutorial-i18n.md) のセクションで読むことが出来ます。 diff --git a/docs/guide-ja/structure-assets.md b/docs/guide-ja/structure-assets.md index 4d42f5faf8e..d9ad0a52915 100644 --- a/docs/guide-ja/structure-assets.md +++ b/docs/guide-ja/structure-assets.md @@ -208,7 +208,7 @@ class FontAwesomeAsset extends AssetBundle ### Bower と NPM のアセットのインストール ほとんどの JavaScript/CSS パッケージは、[Bower](https://bower.io/) および/または [NPM](https://www.npmjs.com/) によって管理されています。 -PHP の世界には PHP の依存を管理する Composer がありますが、PHP のパッケージと全く同じように +PHP の世界には Composer があって、PHP の依存を管理していますが、PHP のパッケージと全く同じように `composer.json` を使って Bower のパッケージも NPM のパッケージもロードすることが可能です。 このことを達成するために Composer の構成を少し修正しなければなりません。二つの方法があります。 diff --git a/docs/guide-ja/structure-controllers.md b/docs/guide-ja/structure-controllers.md index a0cac8d8ed7..d67367d42bf 100644 --- a/docs/guide-ja/structure-controllers.md +++ b/docs/guide-ja/structure-controllers.md @@ -1,7 +1,7 @@ コントローラ ============ -コントローラは [MVC](https://ja.wikipedia.org/wiki/Model_View_Controller) アーキテクチャの一部を成すものです。 +コントローラは [MVC](https://ja.wikipedia.org/wiki/Model_View_Controller) アーキテクチャの一部を構成するものです。 それは [[yii\base\Controller]] を拡張したクラスのオブジェクトであり、リクエストの処理とレスポンスの生成について責任を負います。 具体的には、コントローラは、[アプリケーション](structure-applications.md) から制御を引き継いだ後、 入ってきたリクエストのデータを分析し、それを [モデル](structure-models.md) に引き渡して、 diff --git a/docs/guide-ja/structure-extensions.md b/docs/guide-ja/structure-extensions.md index 66f47e30090..f2122ec43e0 100644 --- a/docs/guide-ja/structure-extensions.md +++ b/docs/guide-ja/structure-extensions.md @@ -75,7 +75,7 @@ Image::thumbnail('@webroot/img/test-image.jpg', 120, 120) 2. もし有れば、エクステンションによって提供されているクラス・オートローダをインストールする。 3. 指示に従って、依存するエクステンションを全てダウンロードしインストールする。 -エクステンションがクラス・オートローダを持っていなくても、[PSR-4 標準](https://www.php-fig.org/psr/psr-4/) に従っている場合は、Yii によって提供されているクラス・オートローダを使ってエクステンションのクラスをオートロードすることが出来ます。 +エクステンションがクラス・オートローダを持っていなくても、[PSR-4 標準](https://www.php-fig.org/psr/psr-4/) に従っている場合は、Yii が提供しているクラス・オートローダを使ってエクステンションのクラスをオートロードすることが出来ます。 必要なことは、エクステンションのルート・ディレクトリのための [ルート・エイリアス](concept-aliases.md#defining-aliases) を宣言することだけです。 例えば、エクステンションを `vendor/mycompany/myext` というディレクトリにインストールしたと仮定します。 そして、エクステンションのクラスは `myext` 名前空間の下にあるとします。 @@ -184,7 +184,7 @@ Yii のアプリケーションは、このファイルによって、どんな それぞれの依存パッケージについて、適切なバージョン制約 (例えば `1.*` や `@stable`) を指定することも忘れてはなりません。 あなたのエクステンションを安定バージョンとしてリリースする場合は、安定した依存パッケージを使ってください。 -たいていの JavaScript/CSS パッケージは、Composer ではなく、[Bower](https://bower.io/) および/または [NPM](https://www.npmjs.com/) を使って管理されています。 +たいていの JavaScript/CSS パッケージは、Composer の代りに、[Bower](https://bower.io/) および/または [NPM](https://www.npmjs.com/) を使って管理されています。 Yii は [Composer アセット・プラグイン](https://github.com/fxpio/composer-asset-plugin) を使って、この種のパッケージを Composer によって管理することを可能にしています。 あなたのエクステンションが Bower パッケージに依存している場合でも、次のように、 `composer.json` に依存パッケージをリストアップすることが簡単に出来ます。 diff --git a/docs/guide-ja/structure-filters.md b/docs/guide-ja/structure-filters.md index dd7e097648f..701765ae8a8 100644 --- a/docs/guide-ja/structure-filters.md +++ b/docs/guide-ja/structure-filters.md @@ -142,7 +142,7 @@ public function behaviors() ### 認証メソッド・フィルタ 認証メソッド・フィルタは、[HTTP Basic 認証](https://ja.wikipedia.org/wiki/Basic%E8%AA%8D%E8%A8%BC)、 -[OAuth 2](https://oauth.net/2/) など、様々なメソッドを使ってユーザを認証するために使われるものです。 +[OAuth 2](https://oauth.net/2/) などの様々なメソッドを使ってユーザを認証するために使われるものです。 これらのフィルタ・クラスはすべて `yii\filters\auth` 名前空間の下にあります。 次の例は、[[yii\filters\auth\HttpBasicAuth]] の使い方を示すもので、HTTP Basic 認証に基づくアクセス・トークンを使ってユーザを認証しています。 diff --git a/docs/guide-ja/structure-models.md b/docs/guide-ja/structure-models.md index dcbc102ead9..80e65d07afa 100644 --- a/docs/guide-ja/structure-models.md +++ b/docs/guide-ja/structure-models.md @@ -293,6 +293,8 @@ public function rules() // "login" シナリオでは、username と password が必須 [['username', 'password'], 'required', 'on' => self::SCENARIO_LOGIN], + + [['username'], 'string'], // username は文字列でなければならない。この規則は全てのシナリオに適用される ]; } ``` diff --git a/docs/guide-ja/test-fixtures.md b/docs/guide-ja/test-fixtures.md index 953b7974787..9a8bc4ed6d8 100644 --- a/docs/guide-ja/test-fixtures.md +++ b/docs/guide-ja/test-fixtures.md @@ -107,8 +107,8 @@ DB と関係しないフィクスチャ (例えば、何らかのファイルや ## フィクスチャを使用する -[Codeception](https://codeception.com/) を使ってコードをテストしている場合は、フィクスチャのローディングとアクセスについては、 -内蔵されているサポートを使用することが出来ます。 +[Codeception](https://codeception.com/) を使ってコードをテストしている場合は、 +フィクスチャのローディングとアクセスについて、内蔵されているサポートを使用することが出来ます。 その他のテスト・フレームワークを使っている場合は、テスト・ケースで [[yii\test\FixtureTrait]] を使って同じ目的を達することが出来ます。 diff --git a/docs/guide-ja/test-functional.md b/docs/guide-ja/test-functional.md index 8a6274e9065..9a08f90653c 100644 --- a/docs/guide-ja/test-functional.md +++ b/docs/guide-ja/test-functional.md @@ -9,7 +9,7 @@ POST や GET のパラメータなどの環境変数を設定しておいてか 経験則から言うと、特別なウェブ・サーバ設定や JavaScript による複雑な UI を持たない場合は、 機能テストの方を選ぶべきです。 -機能テストは Codeception フレームワークの助けを借りて実装されています。これにつては、優れたドキュメントがあります。 +機能テストは Codeception フレームワークの助けを借りて実装されています。これについては、優れたドキュメントがあります。 - [Codeception for Yii framework](https://codeception.com/for/yii) - [Codeception Functional Tests](https://codeception.com/docs/04-FunctionalTests) diff --git a/docs/guide-ja/test-overview.md b/docs/guide-ja/test-overview.md index 41b75812154..89f82ce8c25 100644 --- a/docs/guide-ja/test-overview.md +++ b/docs/guide-ja/test-overview.md @@ -61,7 +61,7 @@ どんな形式の自動化テストもやりすぎになる、という場合もあるでしょう。 -- プロジェクトは単純で、この先も、複雑になる心配はない。 +- プロジェクトが単純で、この先も、複雑になる心配はない。 - これ以上かかわることはない一度限りのプロジェクトである。 ただ、このような場合であっても、時間に余裕があれば、テストを自動化することは良いことです。 diff --git a/docs/guide-ja/test-unit.md b/docs/guide-ja/test-unit.md index 77e596b8f84..2f6caca5cb1 100644 --- a/docs/guide-ja/test-unit.md +++ b/docs/guide-ja/test-unit.md @@ -9,7 +9,7 @@ Yii における単体テストは、PHPUnit と Codeception (こちらはオプ - [Codeception for Yii framework](https://codeception.com/for/yii) - [Codeception Unit Tests](https://codeception.com/docs/05-UnitTests) -- [PHPUnit のドキュメントの第2章以降](https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html). +- [第2章から始まる PHPUnit のドキュメント](https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html) ## ベーシック・テンプレート、アドバンスト・テンプレートのテストを実行する diff --git a/docs/guide-ja/tutorial-console.md b/docs/guide-ja/tutorial-console.md index 74c3401412f..1c39707307b 100644 --- a/docs/guide-ja/tutorial-console.md +++ b/docs/guide-ja/tutorial-console.md @@ -113,11 +113,11 @@ exit($exitCode); -------------------------- シェルで作業をしている場合、コマンド引数の自動補完は便利なものです。 -2.0.11 以降、`./yii` コマンドは、内蔵で Bash および ZSH のために補完をサポートしています。 +2.0.11 以降、`./yii` コマンドは、Bash および ZSH のための自動補完を内蔵でサポートしています。 ### Bash の補完 -bash completion がインストールされていることを確認して下さい。ほとんどの bash のインストレーションでは、デフォルトで利用可能になっています。 +bash completion がインストールされていることを確認して下さい。ほとんどのインストレーションでは、デフォルトで利用可能になっています。 補完スクリプトを `/etc/bash_completion.d/` に置いて下さい。 diff --git a/docs/guide-ja/tutorial-core-validators.md b/docs/guide-ja/tutorial-core-validators.md index 8379daf470b..b51a5a7827e 100644 --- a/docs/guide-ja/tutorial-core-validators.md +++ b/docs/guide-ja/tutorial-core-validators.md @@ -53,7 +53,7 @@ public function rules() このバリデータは、通常、[[yii\captcha\CaptchaAction]] および [[yii\captcha\Captcha]] と一緒に使われ、 入力値が [[yii\captcha\Captcha|CAPTCHA]] ウィジェットによって表示された検証コードと同じであることを確認します。 -- `caseSensitive`: 検証コードの比較で大文字と小文字を区別するかどうか。デフォルト値は `false`。 +- `caseSensitive`: 検証コードの比較で大文字と小文字を区別するか否か。デフォルト値は `false`。 - `captchaAction`: CAPTCHA 画像を表示する [[yii\captcha\CaptchaAction|CAPTCHA アクション]] に対応する [ルート](structure-controllers.md#routes)。デフォルト値は `'site/captcha'`。 - `skipOnEmpty`: 入力値が空のときに検証をスキップできるかどうか。デフォルト値は `false` で、 @@ -113,8 +113,8 @@ compare バリデータは、文字列や数値を比較するためにしか使 ['fromDate', 'compare', 'compareAttribute' => 'toDate', 'operator' => '<', 'enableClientValidation' => false], ``` -バリデータは指定された順序に従って実行されますので、まず最初に、`fromDate` と `toDate` に入力された値が有効な日付であることが確認されます。 -そして、有効な日付であった場合は、機械が読める形式に変換されます。 +バリデータは指定された順序に従って実行されますので、まず最初に、`fromDate` と `toDate` に入力された値が +有効な日付であることが確認され、有効な日付であった場合は、機械が読める形式に変換されます。 その後に、これらの二つの値が compare バリデータによって比較されます。 現在、date バリデータはクライアント・サイドのバリデーションを提供していませんので、これはサーバ・サイドでのみ動作します。 そのため、compare バリデータについても、[[yii\validators\CompareValidator::$enableClientValidation|$enableClientValidation]] は @@ -289,7 +289,7 @@ function foo($model, $attribute) { // 以下と同義 ['a1', 'exist', 'targetAttribute' => ['a2' => 'a2']], - // a1 と a2 の両方が存在する必要がある。両者はともにエラー・メッセージを受け取る + // a1 と a2 の両方が存在する必要がある。エラーの無い最初の属性がエラー・メッセージを受け取る // すなわち、a1 = 3, a2 = 4 は、"a1" カラムに 3, "a2" カラムに 4 が存在する場合に有効 [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']], // 以下と同義 @@ -328,6 +328,8 @@ function foo($model, $attribute) { このバリデータは、一つまたは複数のカラムに対する検証に使用することが出来ます (複数のカラムに対する検証の場合は、それらの属性の組み合せが存在しなければならないことを意味します)。 +同時に複数のカラムをチェックして(例えば `['a1', 'a2']`)バリデーションが失敗したときに、`skipOnError` が `true` に設定されている場合は、 +先行するエラーが無い最初の属性だけが新しいエラー・メッセージを受け取ります。 - `targetClass`: 検証される入力値を探すために使用される [アクティブ・レコード](db-active-record.md) クラスの名前。 設定されていない場合は、現在検証されているモデルのクラスが使用されます。 @@ -418,8 +420,8 @@ function foo($model, $attribute) { - `filter`: フィルタを定義する PHP コールバック。これには、グローバル関数の名前、無名関数などを指定することが出来ます。 関数のシグニチャは ``function ($value) { return $newValue; }` でなければなりません。このプロパティは必須項目です。 - `skipOnArray`: 入力値が配列である場合にフィルタをスキップするか否か。デフォルト値は `false`。 - フィルタが配列の入力を処理できない場合は、このプロパティを `true` に設定しなければなりません。 - そうしないと、何らかの PHP エラーが生じ得ます。 + フィルタが配列の入力を処理できない場合は、このプロパティを `true` に設定しなければなりません。そうしないと、 + 何らかの PHP エラーが生じ得ます。 > Tip: 入力値をトリムしたい場合は、[trim](#trim) バリデータを直接使うことが出来ます。 @@ -679,7 +681,7 @@ IPv4 アドレス `192.168.10.128` も、制約の前にリストされている ['a1', 'unique', 'targetAttribute' => 'a1'], ['a1', 'unique', 'targetAttribute' => ['a1' => 'a1']], - // a1 の入力値が a2 のカラムにおいてユニークである必要がある + // a1 の入力値がユニークである必要がある。ただし a2 のカラムが a1 の入力値のユニークネスのチェックに用いられる // すなわち、a1 = 2 は、"a2" カラムに 2 の値が存在しない場合に有効 ['a1', 'unique', 'targetAttribute' => 'a2'], // 以下と同義 @@ -704,9 +706,10 @@ IPv4 アドレス `192.168.10.128` も、制約の前にリストされている ] ``` -このバリデータは、入力値がテーブルのカラムにおいてユニークであるかどうかをチェックします。 -[アクティブ・レコード](db-active-record.md) モデルの属性に対してのみ働きます。 +このバリデータは、入力値がテーブルのカラムにおいてユニークであるかどうかをチェックします。このバリデータは [アクティブ・レコード](db-active-record.md) モデルの属性に対してのみ働きます。 一つのカラムに対する検証か、複数のカラムに対する検証か、どちらかをサポートします。 +同時に複数のカラムをチェックするバリデーション(例えば上記の `['a1', 'a2']` )が失敗したときに、 +`skipOnError` が `true` に設定されている場合は、先行するエラーが無い最初の属性のみが新しいエラー・メッセージを受け取ります。 - `targetClass`: 検証される入力値を探すために使用される [アクティブ・レコード](db-active-record.md) クラスの名前。 設定されていない場合は、現在検証されているモデルのクラスが使用されます。 diff --git a/docs/guide-ja/tutorial-docker.md b/docs/guide-ja/tutorial-docker.md index 53864ab6144..d52d1a0793f 100644 --- a/docs/guide-ja/tutorial-docker.md +++ b/docs/guide-ja/tutorial-docker.md @@ -24,7 +24,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS これは Docker デーモンが起動して走っていることを意味します。 -これに加えて `docker-compose version` を実行すると、出力は次のようになるはずです。 +さらに、`docker-compose version` を実行すると、出力は次のようになるはずです。 ``` docker-compose version 1.20.0, build unknown diff --git a/docs/guide-ja/tutorial-i18n.md b/docs/guide-ja/tutorial-i18n.md index 43a43f14200..50ba4bf163a 100644 --- a/docs/guide-ja/tutorial-i18n.md +++ b/docs/guide-ja/tutorial-i18n.md @@ -156,8 +156,8 @@ return [ ##### 他のストレージ・タイプ -翻訳メッセージを格納するのには、PHP ファイル以外に、 -次のメッセージ・ソースを使うことも可能です。 +翻訳メッセージを格納するのには、PHP ファイル以外に、次のメッセージ・ソースを +使うことも可能です。 - [[yii\i18n\GettextMessageSource]] - 翻訳メッセージを保持するのに GNU Gettext の MO ファイルまたは PO ファイルを使用する - [[yii\i18n\DbMessageSource]] - 翻訳メッセージを保存するのにデータベース・テーブルを使用する @@ -743,7 +743,7 @@ class TranslationEventHandler 翻訳は [[yii\i18n\PhpMessageSource|php ファイル]]、[[yii\i18n\GettextMessageSource|.po ファイル]]、または [[yii\i18n\DbMessageSource|database]] に保存することが出来ます。追加のオプションについてはそれぞれのクラスを参照してください。 -まず最初に、構成情報ファイルを作成する必要があります。 +最初に、構成情報ファイルを作成する必要があります。 どこに保存したいかを決めて、次のコマンドを発行してください。 ```bash diff --git a/docs/guide-ja/tutorial-mailing.md b/docs/guide-ja/tutorial-mailing.md index 5c8e9dd8d4a..1f7ec10f362 100644 --- a/docs/guide-ja/tutorial-mailing.md +++ b/docs/guide-ja/tutorial-mailing.md @@ -8,7 +8,7 @@ Yii は電子メールの作成と送信をサポートしています。 実際のメール送信メカニズムはエクステンションによって提供されなければなりません。 と言うのは、メール送信はプロジェクトが異なるごとに異なる実装が必要とされるでしょうし、通常、外部のサービスやライブラリに依存するものだからです。 -ごく一般的な場合であれば、[yii2-swiftmailer](https://www.yiiframework.com/extension/yiisoft/yii2-swiftmailer) 公式エクステンションを使用することが出来ます。 +ごく一般的な場合であれば、yii2-symfonymailer](https://www.yiiframework.com/extension/yiisoft/yii2-symfonymailer) 公式エクステンションを使用することが出来ます。 構成 @@ -22,16 +22,11 @@ return [ //.... 'components' => [ 'mailer' => [ - 'class' => 'yii\swiftmailer\Mailer', + 'class' => 'yii\symfonymailer\Mailer', 'useFileTransport' => false, 'transport' => [ - 'class' => 'Swift_SmtpTransport', - 'encryption' => 'tls', - 'host' => 'your_mail_server_host', - 'port' => 'your_smtp_port', - 'username' => 'your_username', - 'password' => 'your_password', - ], + 'dsn' => 'smtp://user:pass@smtp.example.com:465', + ], ], ], ]; diff --git a/docs/guide-ja/tutorial-template-engines.md b/docs/guide-ja/tutorial-template-engines.md index 8791d5bff05..39518d13ad1 100644 --- a/docs/guide-ja/tutorial-template-engines.md +++ b/docs/guide-ja/tutorial-template-engines.md @@ -34,8 +34,8 @@ ] ``` -上記のコードにおいては、Smarty と Twig の両者がビュー・ファイルによって使用可能なものとして構成されています。 -しかし、これらのエクステンションをプロジェクトで使うためには、`composer.json` ファイルも修正して、これらのエクステンションを含める必要があります。 +上記のコードにおいては、Smarty と Twig の両者がビュー・ファイルによって使用可能なものとして構成されています。しかし、 +これらのエクステンションをプロジェクトで使うためには、`composer.json` ファイルも修正して、これらのエクステンションを含める必要があります。 ``` "yiisoft/yii2-smarty": "~2.0.0", diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 1e2ae1d83c3..f59e558bedb 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -10,7 +10,10 @@ Yii Framework 2 Change Log 2.0.50 under development ------------------------ -- Bug #20045: Fix type `boolean` in `MySQL` (terabytesoftw) + +- Bug #17181: Improved `BaseUrl::isRelative($url)` performance (sammousa, bizley, rob006) +- Bug #17191: Fixed `BaseUrl::isRelative($url)` method in `yii\helpers\BaseUrl` (ggh2e3) +- Bug #18469: Fixed `Link::serialize(array $links)` method in `yii\web\Link` (ggh2e3) - Bug #20040: Fix type `boolean` in `MSSQL` (terabytesoftw) - Bug #20005: Fix `yii\console\controllers\ServeController` to specify the router script (terabytesoftw) - Bug #19060: Fix `yii\widgets\Menu` bug when using Closure for active item and adding additional tests in `tests\framework\widgets\MenuTest` (atrandafir) diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php index 7900585bf5e..390494271fd 100644 --- a/framework/caching/Cache.php +++ b/framework/caching/Cache.php @@ -258,21 +258,7 @@ public function multiSet($items, $duration = null, $dependency = null) $duration = $this->defaultDuration; } - if ($dependency !== null && $this->serializer !== false) { - $dependency->evaluateDependency($this); - } - - $data = []; - foreach ($items as $key => $value) { - if ($this->serializer === null) { - $value = serialize([$value, $dependency]); - } elseif ($this->serializer !== false) { - $value = call_user_func($this->serializer[0], [$value, $dependency]); - } - - $key = $this->buildKey($key); - $data[$key] = $value; - } + $data = $this->prepareCacheData($items, $dependency); return $this->setValues($data, $duration); } @@ -290,6 +276,21 @@ public function multiSet($items, $duration = null, $dependency = null) * @since 2.0.7 */ public function multiAdd($items, $duration = 0, $dependency = null) + { + $data = $this->prepareCacheData($items, $dependency); + + return $this->addValues($data, $duration); + } + + /** + * Prepares data for caching by serializing values and evaluating dependencies. + * + * @param array $items The items to be cached. + * @param mixed $dependency The dependency to be evaluated. + * + * @return array The prepared data for caching. + */ + private function prepareCacheData($items, $dependency) { if ($dependency !== null && $this->serializer !== false) { $dependency->evaluateDependency($this); @@ -307,7 +308,7 @@ public function multiAdd($items, $duration = 0, $dependency = null) $data[$key] = $value; } - return $this->addValues($data, $duration); + return $data; } /** diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index 7baa338ce77..48c14a573d1 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -1805,7 +1805,7 @@ private function isValueDifferent($newValue, $oldValue) * - active record instance represented by array (i.e. active record was loaded using [[ActiveQuery::asArray()]]). * @param string|array $relationNames the names of the relations of primary models to be loaded from database. See [[ActiveQueryInterface::with()]] on how to specify this argument. * @param bool $asArray whether to load each related model as an array or an object (if the relation itself does not specify that). - * @since 2.0.49 + * @since 2.0.50 */ public static function loadRelationsFor(&$models, $relationNames, $asArray = false) { @@ -1832,7 +1832,7 @@ public static function loadRelationsFor(&$models, $relationNames, $asArray = fal * * @param string|array $relationNames the names of the relations of this model to be loaded from database. See [[ActiveQueryInterface::with()]] on how to specify this argument. * @param bool $asArray whether to load each relation as an array or an object (if the relation itself does not specify that). - * @since 2.0.49 + * @since 2.0.50 */ public function loadRelations($relationNames, $asArray = false) { diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index 846e5f33cef..38dec4ca036 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -279,7 +279,7 @@ protected function loadColumnSchema($info) if (isset($values[1])) { $column->scale = (int) $values[1]; } - if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + if ($column->size === 1 && $type === 'bit') { $column->type = 'boolean'; } elseif ($type === 'bit') { if ($column->size > 32) { diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php index 21a1f9e6432..749f92f79cd 100644 --- a/framework/helpers/BaseStringHelper.php +++ b/framework/helpers/BaseStringHelper.php @@ -546,14 +546,13 @@ public static function findBetween($string, $start, $end) return null; } - // Cut the string from the start position - $subString = mb_substr($string, $startPos + mb_strlen($start)); - $endPos = mb_strrpos($subString, $end); + $startPos += mb_strlen($start); + $endPos = mb_strrpos($string, $end, $startPos); if ($endPos === false) { return null; } - return mb_substr($subString, 0, $endPos); + return mb_substr($string, $startPos, $endPos - $startPos); } } diff --git a/framework/helpers/BaseUrl.php b/framework/helpers/BaseUrl.php index a671123c7cd..864dbc63c48 100644 --- a/framework/helpers/BaseUrl.php +++ b/framework/helpers/BaseUrl.php @@ -378,7 +378,7 @@ public static function home($scheme = false) */ public static function isRelative($url) { - return strncmp($url, '//', 2) && strpos($url, '://') === false; + return preg_match('~^[[:alpha:]][[:alnum:]+-.]*://|^//~', $url) === 0; } /** diff --git a/framework/messages/ja/yii.php b/framework/messages/ja/yii.php index 6fd6c3f9da5..b1b0f43cab8 100644 --- a/framework/messages/ja/yii.php +++ b/framework/messages/ja/yii.php @@ -26,8 +26,8 @@ ' and ' => ' および ', '"{attribute}" does not support operator "{operator}".' => '"{attribute}" は演算子 "{operator}" をサポートしていません。', '(not set)' => '(未設定)', - 'Action not found.' => '', - 'Aliases available: {aliases}' => '', + 'Action not found.' => 'アクションがありません。', + 'Aliases available: {aliases}' => '利用可能なエイリアス: {aliases}', 'An internal server error occurred.' => '内部サーバーエラーが発生しました。', 'Are you sure you want to delete this item?' => 'このアイテムを削除したいというのは本当ですか?', 'Condition for "{attribute}" should be either a value or valid operator specification.' => '"{attribute}" のための条件は値であるか有効な演算子の定義でなければなりません。', @@ -45,7 +45,7 @@ 'Only files with these extensions are allowed: {extensions}.' => '次の拡張子を持つファイルだけが許可されています : {extensions}', 'Operator "{operator}" must be used with a search attribute.' => '演算子 "{operator}" はサーチ属性とともに使用されなければなりません。', 'Operator "{operator}" requires multiple operands.' => '演算子 "{operator}" は複数の被演算子を要求します。', - 'Options available: {options}' => '', + 'Options available: {options}' => '利用可能なオプション: {options}', 'Page not found.' => 'ページが見つかりません。', 'Please fix the following errors:' => '次のエラーを修正してください :', 'Please upload a file.' => 'ファイルをアップロードしてください。', @@ -109,7 +109,7 @@ '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} は {min} 文字以上でなければいけません。', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} は {max} 文字以下でなければいけません。', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} は {length} 文字でなければいけません。', - '{compareAttribute} is invalid.' => '', + '{compareAttribute} is invalid.' => '{compareAttribute} は無効です。', '{delta, plural, =1{1 day} other{# days}}' => '{delta} 日間', '{delta, plural, =1{1 hour} other{# hours}}' => '{delta} 時間', '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta} 分間', diff --git a/framework/web/Link.php b/framework/web/Link.php index c64c34f81c5..3f90fc66a28 100644 --- a/framework/web/Link.php +++ b/framework/web/Link.php @@ -62,11 +62,10 @@ public static function serialize(array $links) { foreach ($links as $rel => $link) { if (is_array($link)) { - foreach ($link as $i => $l) { - $link[$i] = $l instanceof self ? array_filter((array) $l) : ['href' => $l]; - } - $links[$rel] = $link; - } elseif (!$link instanceof self) { + $links[$rel] = self::serialize($link); + } elseif ($link instanceof self) { + $links[$rel] = array_filter((array)$link); + } else { $links[$rel] = ['href' => $link]; } } diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php index fddb8b342d0..1244a6cbb33 100644 --- a/framework/web/UrlManager.php +++ b/framework/web/UrlManager.php @@ -413,7 +413,7 @@ public function createUrl($params) $anchor = isset($params['#']) ? '#' . $params['#'] : ''; unset($params['#'], $params[$this->routeParam]); - $route = trim($params[0], '/'); + $route = trim(isset($params[0]) ? $params[0] : '', '/'); unset($params[0]); $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl(); diff --git a/tests/framework/data/BaseDataProviderTest.php b/tests/framework/data/BaseDataProviderTest.php index 392b2eb9144..c88a33a4425 100644 --- a/tests/framework/data/BaseDataProviderTest.php +++ b/tests/framework/data/BaseDataProviderTest.php @@ -20,7 +20,7 @@ public function testGenerateId(): void $rc = new \ReflectionClass(BaseDataProvider::class); $rp = $rc->getProperty('counter'); $rp->setAccessible(true); - $rp->setValue(null); + $rp->setValue(new ConcreteDataProvider(), null); $this->assertNull((new ConcreteDataProvider())->id); $this->assertNotNull((new ConcreteDataProvider())->id); diff --git a/tests/framework/db/mysql/SchemaTest.php b/tests/framework/db/mysql/SchemaTest.php index cea30132343..1c39a439263 100644 --- a/tests/framework/db/mysql/SchemaTest.php +++ b/tests/framework/db/mysql/SchemaTest.php @@ -228,11 +228,6 @@ public function getExpectedColumns() ] ); - $columns['bool_col']['type'] = 'boolean'; - $columns['bool_col']['phpType'] = 'boolean'; - $columns['bool_col2']['type'] = 'boolean'; - $columns['bool_col2']['phpType'] = 'boolean'; - if (version_compare($version, '5.7', '<')) { $columns['int_col3']['phpType'] = 'string'; $columns['json_col']['type'] = 'text'; diff --git a/tests/framework/db/mysql/type/BooleanTest.php b/tests/framework/db/mysql/type/BooleanTest.php deleted file mode 100644 index bc9d307a78d..00000000000 --- a/tests/framework/db/mysql/type/BooleanTest.php +++ /dev/null @@ -1,249 +0,0 @@ -getConnection(true); - $schema = $db->getSchema(); - $tableName = '{{%boolean}}'; - - if ($db->getTableSchema($tableName)) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable( - $tableName, - [ - 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), - 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), - 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), - ] - )->execute(); - - // test type `boolean` - $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); - $this->assertSame('boolean', $columnBoolColTinyint->phpType); - - $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); - $this->assertSame('boolean', $columnBoolColBit->phpType); - - // test value `false` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => false, 'bool_col_bit' => false])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); - $this->assertEquals(0, $boolValues['bool_col_tinyint']); - $this->assertEquals(0, $boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertFalse($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertFalse($phpTypeCastBoolColBit); - - // test value `true` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => true, 'bool_col_bit' => true])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 2")->queryOne(); - $this->assertEquals(1, $boolValues['bool_col_tinyint']); - $this->assertEquals(1, $boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertTrue($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertTrue($phpTypeCastBoolColBit); - } - - public function testBooleanWithValueInteger() - { - $db = $this->getConnection(true); - $schema = $db->getSchema(); - $tableName = '{{%boolean}}'; - - if ($db->getTableSchema($tableName)) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable( - $tableName, - [ - 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), - 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), - 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), - ] - )->execute(); - - // test type `boolean` - $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); - $this->assertSame('boolean', $columnBoolColTinyint->phpType); - - $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); - $this->assertSame('boolean', $columnBoolColBit->phpType); - - // test value `0` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 0, 'bool_col_bit' => 0])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); - $this->assertEquals(0, $boolValues['bool_col_tinyint']); - $this->assertEquals(0, $boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertFalse($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertFalse($phpTypeCastBoolColBit); - - // test value `1` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 1, 'bool_col_bit' => 1])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 2")->queryOne(); - $this->assertEquals(1, $boolValues['bool_col_tinyint']); - $this->assertEquals(1, $boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertTrue($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertTrue($phpTypeCastBoolColBit); - } - - public function testBooleanWithValueNegative() - { - $db = $this->getConnection(true); - $schema = $db->getSchema(); - $tableName = '{{%boolean}}'; - - if ($db->getTableSchema($tableName)) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable( - $tableName, - [ - 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), - 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), - 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), - ] - )->execute(); - - // test type `boolean` - $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); - $this->assertSame('boolean', $columnBoolColTinyint->phpType); - - $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); - $this->assertSame('boolean', $columnBoolColBit->phpType); - - // test value `-1` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => -1, 'bool_col_bit' => -1])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); - - $this->assertEquals(1, $boolValues['bool_col_tinyint']); - $this->assertEquals(1, $boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertTrue($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertTrue($phpTypeCastBoolColBit); - } - - public function testBooleanWithValueNull() - { - $db = $this->getConnection(true); - $schema = $db->getSchema(); - $tableName = '{{%boolean}}'; - - if ($db->getTableSchema($tableName)) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable( - $tableName, - [ - 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), - 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), - 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), - ] - )->execute(); - - // test type `boolean` - $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); - $this->assertSame('boolean', $columnBoolColTinyint->phpType); - - $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); - $this->assertSame('boolean', $columnBoolColBit->phpType); - - // test value `null` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => null, 'bool_col_bit' => null])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); - - $this->assertNull($boolValues['bool_col_tinyint']); - $this->assertNull($boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertNull($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertNull($phpTypeCastBoolColBit); - } - - public function testBooleanWithValueOverflow() - { - $db = $this->getConnection(true); - $schema = $db->getSchema(); - $tableName = '{{%boolean}}'; - - if ($db->getTableSchema($tableName)) { - $db->createCommand()->dropTable($tableName)->execute(); - } - - $db->createCommand()->createTable( - $tableName, - [ - 'id' => $schema->createColumnSchemaBuilder(Schema::TYPE_PK), - 'bool_col_tinyint' => $schema->createColumnSchemaBuilder(Schema::TYPE_BOOLEAN), - 'bool_col_bit' => $schema->createColumnSchemaBuilder('bit', 1), - ] - )->execute(); - - // test type `boolean` - $columnBoolColTinyint = $db->getTableSchema($tableName)->getColumn('bool_col_tinyint'); - $this->assertSame('boolean', $columnBoolColTinyint->phpType); - - $columnBoolColBit = $db->getTableSchema($tableName)->getColumn('bool_col_bit'); - $this->assertSame('boolean', $columnBoolColBit->phpType); - - // test value `2` - $db->createCommand()->insert($tableName, ['bool_col_tinyint' => 2, 'bool_col_bit' => 2])->execute(); - $boolValues = $db->createCommand("SELECT * FROM $tableName WHERE id = 1")->queryOne(); - - $this->assertEquals(1, $boolValues['bool_col_tinyint']); - $this->assertEquals(1, $boolValues['bool_col_bit']); - - // test php typecast - $phpTypeCastBoolColTinyint = $columnBoolColTinyint->phpTypecast($boolValues['bool_col_tinyint']); - $this->assertTrue($phpTypeCastBoolColTinyint); - - $phpTypeCastBoolColBit = $columnBoolColBit->phpTypecast($boolValues['bool_col_bit']); - $this->assertTrue($phpTypeCastBoolColBit); - } -} diff --git a/tests/framework/helpers/BaseUrlTest.php b/tests/framework/helpers/BaseUrlTest.php new file mode 100644 index 00000000000..9bcd39ef981 --- /dev/null +++ b/tests/framework/helpers/BaseUrlTest.php @@ -0,0 +1,116 @@ +assertTrue(BaseUrl::isRelative($url)); + } + + /** @dataProvider relativeFalseUrlProvider */ + public function testIsRelativeWillReturnFalse($url) + { + $this->assertFalse(BaseUrl::isRelative($url)); + } + + /** @dataProvider ensureSchemeUrlProvider */ + public function testEnsureScheme($url, $scheme, $expected) + { + $this->assertEquals($expected, BaseUrl::ensureScheme($url, $scheme)); + } + + public function ensureSchemeUrlProvider() + { + return [ + 'relative url and https scheme will return input url' => [ + 'url' => 'acme.com?name=bugs.bunny', + 'scheme' => 'https', + 'expected result' => 'acme.com?name=bugs.bunny', + ], + 'relative url and another url as parameter will return input url' => [ + 'url' => 'acme.com/test?tnt-link=https://tnt.com/', + 'scheme' => 'https', + 'expected' => 'acme.com/test?tnt-link=https://tnt.com/', + ], + 'url with scheme not a string will return input url' => [ + 'url' => 'acme.com?name=bugs.bunny', + 'scheme' => 123, + 'expected' => 'acme.com?name=bugs.bunny', + ], + 'protocol relative url and https scheme will be processed' => [ + 'url' => '//acme.com?characters/list', + 'scheme' => 'https', + 'expected' => 'https://acme.com?characters/list', + ], + 'protocol relative url and empty scheme will be returned' => [ + 'url' => '//acme.com?characters/list', + 'scheme' => '', + 'expected' => '//acme.com?characters/list', + ], + 'absolute url and empty scheme will create protocol relative url' => [ + 'url' => 'https://acme.com?characters/list', + 'scheme' => '', + 'expected' => '//acme.com?characters/list', + ], + 'absolute url and different scheme will be processed' => [ + 'url' => 'http://acme.com/test?tnt-link=https://tnt.com/', + 'scheme' => 'https', + 'expected' => 'https://acme.com/test?tnt-link=https://tnt.com/', + ] + ]; + } + + public function relativeTrueUrlProvider() + { + return [ + 'url url without protocol' => [ + 'url' => 'acme.com/tnt-room=123', + ], + 'url without protocol and another url in a parameter value' => [ + 'url' => 'acme.com?tnt-room-link=https://tnt.com', + ], + 'path only' => [ + 'url' => '/path', + ], + 'path with param' => [ + 'url' => '/path=/home/user', + ], + ]; + } + + public function relativeFalseUrlProvider() + { + return [ + 'url with https protocol' => [ + 'url' => 'https://acme.com', + ], + 'url with https protocol and ending slash' => [ + 'url' => 'https://acme.com/', + ], + 'url with https protocol and another url as param value' => [ + 'url' => 'https://acme.com?tnt-link=https://tnt.com', + ], + 'url starting with two slashes' => [ + 'url' => '//example.com', + ], + 'url with ftp protocol' => [ + 'url' => 'ftp://ftp.acme.com/tnt-suppliers.txt', + ], + 'url with http protocol' => [ + 'url' => 'http://no-protection.acme.com', + ], + 'file url' => [ + 'url' => 'file:///home/User/2ndFile.html', + ] + ]; + } +} diff --git a/tests/framework/web/LinkTest.php b/tests/framework/web/LinkTest.php new file mode 100644 index 00000000000..6cc99b4d5a2 --- /dev/null +++ b/tests/framework/web/LinkTest.php @@ -0,0 +1,89 @@ + 'https://example.com/users/4', + 'name' => 'User 4', + 'title' => 'My Manager', + ]); + + $expected = [ + 'self' => [ + 'href' => 'https://example.com/users/1' + ], + 'manager' => [ + 'href' => 'https://example.com/users/4', + 'name' => 'User 4', + 'title' => 'My Manager', + ], + ]; + + $this->assertEquals($expected, Link::serialize([ + 'self' => 'https://example.com/users/1', + 'manager' => $managerLink, + ])); + } + + public function testSerializeNestedArrayWithLinkWillSerialize() + { + $linkData = [ + 'self' => new Link([ + 'href' => 'https://example.com/users/3', + 'name' => 'Daffy Duck', + ]), + 'fellows' => [ + [ + new Link([ + 'href' => 'https://example.com/users/4', + 'name' => 'Bugs Bunny', + ]), + ], + [ + new Link([ + 'href' => 'https://example.com/users/5', + 'name' => 'Lola Bunny', + ]), + ] + ] + ]; + + $expected = [ + 'self' => [ + 'href' => 'https://example.com/users/3', + 'name' => 'Daffy Duck', + ], + 'fellows' => [ + [ + [ + 'href' => 'https://example.com/users/4', + 'name' => 'Bugs Bunny', + ] + ], + [ + [ + 'href' => 'https://example.com/users/5', + 'name' => 'Lola Bunny', + ] + ] + ], + ]; + + $this->assertEquals($expected, Link::serialize($linkData)); + } +}