diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c960a0ec68..7dce76c9f5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: run: sudo locale-gen fr_FR.UTF-8 - name: Checkout. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP. uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/ci-mssql.yml b/.github/workflows/ci-mssql.yml index 4d4d8141f43..45e4d497275 100644 --- a/.github/workflows/ci-mssql.yml +++ b/.github/workflows/ci-mssql.yml @@ -43,8 +43,8 @@ jobs: options: --name=mssql --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - name: Checkout. - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 - name: Create MS SQL Database. run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE yiitest' diff --git a/.github/workflows/ci-mysql.yml b/.github/workflows/ci-mysql.yml index e23802004f7..f882ab84e9e 100644 --- a/.github/workflows/ci-mysql.yml +++ b/.github/workflows/ci-mysql.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Checkout. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/ci-node.yml b/.github/workflows/ci-node.yml index 0d937733554..3f49942b84a 100644 --- a/.github/workflows/ci-node.yml +++ b/.github/workflows/ci-node.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies. run: composer update $DEFAULT_COMPOSER_FLAGS diff --git a/.github/workflows/ci-oracle.yml b/.github/workflows/ci-oracle.yml index 130a4d293aa..c471108c6ab 100644 --- a/.github/workflows/ci-oracle.yml +++ b/.github/workflows/ci-oracle.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/ci-pgsql.yml b/.github/workflows/ci-pgsql.yml index 428a53bd5cb..f1ef701ebd0 100644 --- a/.github/workflows/ci-pgsql.yml +++ b/.github/workflows/ci-pgsql.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/ci-sqlite.yml b/.github/workflows/ci-sqlite.yml index 8e06d886488..80a61fe37b4 100644 --- a/.github/workflows/ci-sqlite.yml +++ b/.github/workflows/ci-sqlite.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install PHP with extensions. uses: shivammathur/setup-php@v2 diff --git a/build/controllers/MimeTypeController.php b/build/controllers/MimeTypeController.php index 9da23f67fcd..7986dd56cc8 100644 --- a/build/controllers/MimeTypeController.php +++ b/build/controllers/MimeTypeController.php @@ -121,10 +121,14 @@ private function generateMimeTypesFile($outFile, $content) * Its content is generated from the apache http mime.types file. * https://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=markup * This file has been placed in the public domain for unlimited redistribution. + * + * All extra changes made to this file must be comitted to /build/controllers/MimeTypeController.php + * otherwise they will be lost on next build. */ \$mimeTypes = $array; -if (PHP_VERSION_ID >= 80100) { +# fix for bundled libmagic bug, see also https://github.com/yiisoft/yii2/issues/19925 +if ((PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80122) || (PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80209)) { \$mimeTypes = array_replace(\$mimeTypes, array('xz' => 'application/octet-stream')); } @@ -148,6 +152,9 @@ private function generateMimeAliasesFile($outFile) * MIME aliases. * * This file contains aliases for MIME types. + * + * All extra changes made to this file must be comitted to /build/controllers/MimeTypeController.php + * otherwise they will be lost on next build. */ return $array; @@ -209,6 +216,9 @@ private function generateMimeExtensionsFile($outFile, $content) * Its content is generated from the apache http mime.types file. * https://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=markup * This file has been placed in the public domain for unlimited redistribution. + * + * All extra changes made to this file must be comitted to /build/controllers/MimeTypeController.php + * otherwise they will be lost on next build. */ return $array; diff --git a/composer.json b/composer.json index fc013967d8d..492c6bd6ded 100644 --- a/composer.json +++ b/composer.json @@ -76,8 +76,8 @@ "ezyang/htmlpurifier": "^4.6", "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", - "bower-asset/inputmask": "~3.2.2 | ~3.3.5", - "bower-asset/punycode": "1.3.*", + "bower-asset/inputmask": "~3.2.2 | ~3.3.5 | ~5.0.8 ", + "bower-asset/punycode": "1.3.* | 2.2.*", "bower-asset/yii2-pjax": "~2.0.1", "paragonie/random_compat": ">=1" }, diff --git a/composer.lock b/composer.lock index 80fc9217a2b..5820856694d 100644 --- a/composer.lock +++ b/composer.lock @@ -8,7 +8,7 @@ "packages": [ { "name": "bower-asset/inputmask", - "version": "3.3.11", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/RobinHerbots/Inputmask.git", @@ -16,8 +16,8 @@ }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/5e670ad62f50c738388d4dcec78d2888505ad77b", - "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" + "url": "https://api.github.com/repos/RobinHerbots/Inputmask/zipball/e0f39e0c93569c6b494c3a57edef2c59313a6b64", + "reference": "e0f39e0c93569c6b494c3a57edef2c59313a6b64" }, "require": { "bower-asset/jquery": ">=1.7" @@ -47,7 +47,7 @@ }, { "name": "bower-asset/punycode", - "version": "v1.3.2", + "version": "v2.2.3", "source": { "type": "git", "url": "https://github.com/mathiasbynens/punycode.js.git", @@ -444,16 +444,16 @@ }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", "shasum": "" }, "require": { @@ -503,9 +503,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.0" }, "funding": [ { @@ -521,7 +521,7 @@ "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2023-08-31T09:50:34+00:00" }, { "name": "composer/xdebug-handler", @@ -3274,16 +3274,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -3298,7 +3298,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3336,7 +3336,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -3352,20 +3352,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -3377,7 +3377,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3417,7 +3417,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -3433,20 +3433,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -3458,7 +3458,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3501,7 +3501,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -3517,20 +3517,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -3545,7 +3545,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3584,7 +3584,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -3600,20 +3600,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -3622,7 +3622,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3667,7 +3667,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -3683,20 +3683,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", + "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", "shasum": "" }, "require": { @@ -3705,7 +3705,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3746,7 +3746,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" }, "funding": [ { @@ -3762,7 +3762,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/process", @@ -4120,5 +4120,5 @@ "lib-pcre": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/docs/guide-ru/concept-di-container.md b/docs/guide-ru/concept-di-container.md index 80c2c723d86..2812d4bd8fc 100644 --- a/docs/guide-ru/concept-di-container.md +++ b/docs/guide-ru/concept-di-container.md @@ -498,7 +498,7 @@ class HotelController extends Controller как можно раньше. Ниже приведены рекомендуемые практики: * Если вы разработчик приложения, то вы можете зарегистрировать зависимости в конфигурации вашего приложения. - Как это сделать описано в подразделе [Конфигурация приложения](concept-service-locator.md#application-configurations) + Как это сделать описано в подразделе [Конфигурация приложения](concept-configurations.md#application-configurations) раздела [Конфигурации](concept-configurations.md). * Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости в загрузочном классе расширения. diff --git a/docs/guide-ru/structure-controllers.md b/docs/guide-ru/structure-controllers.md index c55a8cf1517..5765f26c16a 100644 --- a/docs/guide-ru/structure-controllers.md +++ b/docs/guide-ru/structure-controllers.md @@ -310,7 +310,7 @@ class HelloWorldAction extends Action [[yii\console\Response::exitStatus|статус выхода]] исполнения команды. В вышеприведенных примерах, все результаты действий являются строками, которые будут использованы в качестве тела ответа, -высланного пользователю. Следующий пример, показывает действие может перенаправить браузер пользователя на новый URL, с помощью +высланного пользователю. Следующий пример, показывает как действие может перенаправить браузер пользователя на новый URL, с помощью возврата response объекта (т. к. [[yii\web\Controller::redirect()|redirect()]] метод возвращает response объект): ```php diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index bafede12ae1..291575c846f 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -263,6 +263,12 @@ Further reading on the topic: - +Avoiding arbitrary object instantiations +---------------------------------------- + +Yii [configurations](concept-configurations.md) are associative arrays used by the framework to instantiate new objects through `Yii::createObject($config)`. These arrays specify the class name for instantiation, and it is important to ensure that this class name does not originate from untrusted sources. Otherwise, it can lead to Unsafe Reflection, a vulnerability that allows the execution of malicious code by exploiting the loading of specific classes. Additionally, when you need to dynamically add keys to an object derived from a framework class, such as the base `Component` class, it's essential to validate these dynamic properties using a whitelist approach. This precaution is necessary because the framework might employ `Yii::createObject($config)` within the `__set()` magic method. + + Avoiding file exposure ---------------------- diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 91ff0a353c4..dc032ca138a 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -11,9 +11,23 @@ Yii Framework 2 Change Log 2.0.50 under development ------------------------ +- Enh #12743: Added new methods `BaseActiveRecord::loadRelations()` and `BaseActiveRecord::loadRelationsFor()` to eager load related models for existing primary model instances (PowerGamer1) + + +2.0.49.2 October 12, 2023 +------------------------- + - Bug #19925: Improved PHP version check when handling MIME types (schmunk42) + + +2.0.49.1 October 05, 2023 +------------------------- + - Bug #19940: File Log writer without newline (terabytesoftw) +- Bug #19950: Fix `Query::groupBy(null)` causes error for PHP 8.1: `trim(): Passing null to parameter #1 ($string) of type string is deprecated` (uaoleg) - Bug #19951: Removed unneeded MIME file tests (schmunk42) +- Bug #19984: Do not duplicate log messages in memory (lubosdz) +- Enh #19780: added pcntl to requirements check (schmunk42) 2.0.49 August 29, 2023 diff --git a/framework/composer.json b/framework/composer.json index 8d638df3c21..e6da7d26d9e 100644 --- a/framework/composer.json +++ b/framework/composer.json @@ -71,8 +71,8 @@ "ezyang/htmlpurifier": "^4.6", "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", - "bower-asset/inputmask": "~3.2.2 | ~3.3.5", - "bower-asset/punycode": "1.3.*", + "bower-asset/inputmask": "~3.2.2 | ~3.3.5 | ~5.0.8 ", + "bower-asset/punycode": "1.3.* | 2.2.*", "bower-asset/yii2-pjax": "~2.0.1", "paragonie/random_compat": ">=1" }, diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index e1bb4cc2d77..7baa338ce77 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -1786,4 +1786,57 @@ private function isValueDifferent($newValue, $oldValue) return $newValue !== $oldValue; } + + /** + * Eager loads related models for the already loaded primary models. + * + * Helps to reduce the number of queries performed against database if some related models are only used + * when a specific condition is met. For example: + * + * ```php + * $customers = Customer::find()->where(['country_id' => 123])->all(); + * if (Yii:app()->getUser()->getIdentity()->canAccessOrders()) { + * Customer::loadRelationsFor($customers, 'orders.items'); + * } + * ``` + * + * @param array|ActiveRecordInterface[] $models array of primary models. Each model should have the same type and can be: + * - an active record instance; + * - 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 + */ + public static function loadRelationsFor(&$models, $relationNames, $asArray = false) + { + // ActiveQueryTrait::findWith() called below assumes $models array is non-empty. + if (empty($models)) { + return; + } + + static::find()->asArray($asArray)->findWith((array)$relationNames, $models); + } + + /** + * Eager loads related models for the already loaded primary model. + * + * Helps to reduce the number of queries performed against database if some related models are only used + * when a specific condition is met. For example: + * + * ```php + * $customer = Customer::find()->where(['id' => 123])->one(); + * if (Yii:app()->getUser()->getIdentity()->canAccessOrders()) { + * $customer->loadRelations('orders.items'); + * } + * ``` + * + * @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 + */ + public function loadRelations($relationNames, $asArray = false) + { + $models = [$this]; + static::loadRelationsFor($models, $relationNames, $asArray); + } } diff --git a/framework/db/Query.php b/framework/db/Query.php index 9374f9d849c..f44a1c44743 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -996,7 +996,7 @@ public function rightJoin($table, $on = '', $params = []) /** * Sets the GROUP BY part of the query. - * @param string|array|ExpressionInterface $columns the columns to be grouped by. + * @param string|array|ExpressionInterface|null $columns the columns to be grouped by. * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). * The method will automatically quote the column names unless a column contains some parenthesis * (which means the column contains a DB expression). @@ -1014,7 +1014,7 @@ public function groupBy($columns) { if ($columns instanceof ExpressionInterface) { $columns = [$columns]; - } elseif (!is_array($columns)) { + } elseif (!is_array($columns) && !is_null($columns)) { $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); } $this->groupBy = $columns; diff --git a/framework/helpers/mimeAliases.php b/framework/helpers/mimeAliases.php index 4cd89888a77..a9e677adcbd 100644 --- a/framework/helpers/mimeAliases.php +++ b/framework/helpers/mimeAliases.php @@ -3,6 +3,9 @@ * MIME aliases. * * This file contains aliases for MIME types. + * + * All extra changes made to this file must be comitted to /build/controllers/MimeTypeController.php + * otherwise they will be lost on next build. */ return [ 'text/rtf' => 'application/rtf', diff --git a/framework/helpers/mimeExtensions.php b/framework/helpers/mimeExtensions.php index 946d61cd0c5..e4936030fd8 100644 --- a/framework/helpers/mimeExtensions.php +++ b/framework/helpers/mimeExtensions.php @@ -8,6 +8,9 @@ * Its content is generated from the apache http mime.types file. * https://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=markup * This file has been placed in the public domain for unlimited redistribution. + * + * All extra changes made to this file must be comitted to /build/controllers/MimeTypeController.php + * otherwise they will be lost on next build. */ return [ 'application/andrew-inset' => 'ez', diff --git a/framework/helpers/mimeTypes.php b/framework/helpers/mimeTypes.php index e91f80f95f8..f895e8d0728 100644 --- a/framework/helpers/mimeTypes.php +++ b/framework/helpers/mimeTypes.php @@ -7,6 +7,9 @@ * Its content is generated from the apache http mime.types file. * https://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=markup * This file has been placed in the public domain for unlimited redistribution. + * + * All extra changes made to this file must be comitted to /build/controllers/MimeTypeController.php + * otherwise they will be lost on next build. */ $mimeTypes = [ 123 => 'application/vnd.lotus-1-2-3', diff --git a/framework/log/FileTarget.php b/framework/log/FileTarget.php index 29e76d470c6..3e13278a30c 100644 --- a/framework/log/FileTarget.php +++ b/framework/log/FileTarget.php @@ -88,9 +88,8 @@ public function init() public function export() { $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n"; - $trimmedText = trim($text); - if (empty($trimmedText)) { + if (trim($text) === '') { return; // No messages to export, so we exit the function early } diff --git a/framework/messages/ko/yii.php b/framework/messages/ko/yii.php index 4fe82311fe8..b0ad872fe72 100644 --- a/framework/messages/ko/yii.php +++ b/framework/messages/ko/yii.php @@ -75,8 +75,8 @@ '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 크거나 같아야 합니다.', '{attribute} must be less than "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 작아야 합니다.', '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 작거나 같아야 합니다.', - '{attribute} must be no greater than {max}.' => '{attribute}는 "{compareValue}" 보다 클 수 없습니다.', - '{attribute} must be no less than {min}.' => '{attribute}는 "{compareValue}" 보다 작을 수 없습니다.', + '{attribute} must be no greater than {max}.' => '{attribute}는 "{max}" 보다 클 수 없습니다.', + '{attribute} must be no less than {min}.' => '{attribute}는 "{min}" 보다 작을 수 없습니다.', '{attribute} must be repeated exactly.' => '{attribute}는 정확하게 반복합니다.', '{attribute} must not be equal to "{compareValue}".' => '{attribute}는 "{compareValue}"와 같을 수 없습니다.', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}는 최소 {min}자 이어야합니다.', diff --git a/framework/requirements/requirements.php b/framework/requirements/requirements.php index 55593061518..d065f6363bc 100644 --- a/framework/requirements/requirements.php +++ b/framework/requirements/requirements.php @@ -111,5 +111,12 @@ 'memo' => 'When IpValidator::expandIPv6 property is set to true, PHP must support IPv6 protocol stack. Currently PHP constant AF_INET6 is not defined and IPv6 is probably unsupported.' + ), + array( + 'name' => 'pcntl', + 'mandatory' => false, + 'condition' => extension_loaded('pcntl'), + 'by' => 'Process Control', + 'memo' => 'Recommended for yii2-queue CLI operations' ) ); diff --git a/tests/framework/db/ActiveRecordTest.php b/tests/framework/db/ActiveRecordTest.php index cbe4bdaa2d2..fed20b52b5a 100644 --- a/tests/framework/db/ActiveRecordTest.php +++ b/tests/framework/db/ActiveRecordTest.php @@ -2239,6 +2239,53 @@ public function testGetAttributeLabel($model) $attr = 'model2.doesNotExist.attr1'; $this->assertEquals($model->generateAttributeLabel($attr), $model->getAttributeLabel($attr)); } + + public function testLoadRelations() + { + // Test eager loading relations for multiple primary models using loadRelationsFor(). + /** @var Customer[] $customers */ + $customers = Customer::find()->all(); + Customer::loadRelationsFor($customers, ['orders.items']); + foreach ($customers as $customer) { + $this->assertTrue($customer->isRelationPopulated('orders')); + foreach ($customer->orders as $order) { + $this->assertTrue($order->isRelationPopulated('items')); + } + } + + // Test eager loading relations as arrays. + /** @var array $customers */ + $customers = Customer::find()->asArray(true)->all(); + Customer::loadRelationsFor($customers, ['orders.items' => function ($query) { $query->asArray(false); }], true); + foreach ($customers as $customer) { + $this->assertTrue(isset($customer['orders'])); + $this->assertTrue(is_array($customer['orders'])); + foreach ($customer['orders'] as $order) { + $this->assertTrue(is_array($order)); + $this->assertTrue(isset($order['items'])); + $this->assertTrue(is_array($order['items'])); + foreach ($order['items'] as $item) { + $this->assertFalse(is_array($item)); + } + } + } + + // Test eager loading relations for a single primary model using loadRelations(). + /** @var Customer $customer */ + $customer = Customer::find()->where(['id' => 1])->one(); + $customer->loadRelations('orders.items'); + $this->assertTrue($customer->isRelationPopulated('orders')); + foreach ($customer->orders as $order) { + $this->assertTrue($order->isRelationPopulated('items')); + } + + // Test eager loading previously loaded relation (relation value should be replaced with a new value loaded from database). + /** @var Customer $customer */ + $customer = Customer::find()->where(['id' => 2])->with(['orders' => function ($query) { $query->orderBy(['id' => SORT_ASC]); }])->one(); + $this->assertTrue($customer->orders[0]->id < $customer->orders[1]->id, 'Related models should be sorted by ID in ascending order.'); + $customer->loadRelations(['orders' => function ($query) { $query->orderBy(['id' => SORT_DESC]); }]); + $this->assertTrue($customer->orders[0]->id > $customer->orders[1]->id, 'Related models should be sorted by ID in descending order.'); + } } class LabelTestModel1 extends \yii\db\ActiveRecord