From 687f15fbd2968df0e81fd64b639a6351ade41500 Mon Sep 17 00:00:00 2001 From: Dacheng Gao <13791720+successgdc@users.noreply.github.com> Date: Sat, 9 Mar 2019 05:55:30 +0800 Subject: [PATCH 1/5] Remove extra space char in comments (#17198) [skip ci] --- framework/db/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index ba6331ea78..484a5b84ab 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -685,7 +685,7 @@ class QueryBuilder extends \yii\base\BaseObject /** * Builds a SQL statement for creating a new DB table. * - * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), + * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), * where name stands for a column name which will be properly quoted by the method, and definition * stands for the column type which can contain an abstract DB type. * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one. From 5b5150ae62e1e2c0391e968aa18d04ecbf59f69e Mon Sep 17 00:00:00 2001 From: Nikolay Poryadin Date: Sat, 9 Mar 2019 15:35:19 +0300 Subject: [PATCH 2/5] Fixes #17133: Fixed aliases rendering during help generation for a console command --- framework/CHANGELOG.md | 1 + .../console/controllers/HelpController.php | 2 +- tests/framework/console/ControllerTest.php | 5 +++++ .../FakeHelpControllerWithoutOutput.php | 21 +++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/framework/console/FakeHelpControllerWithoutOutput.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 44b978c0cd..c3dbc394d4 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.17 under development ------------------------ +- Bug #17133: Fixed aliases rendering during help generation for a console command (GHopperMSK) - Bug #17185: Fixed `AssetManager` timestamp appending when a file is published manually (GHopperMSK) - Bug #17156: Fixes PHP 7.2 warning when a data provider has no data as a parameter for a GridView (evilito) - Bug #17083: Fixed `yii\validators\EmailValidator::$checkDNS` tells that every domain is correct on alpine linux (mikk150) diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php index 20d695fb0e..0155d2e7c2 100644 --- a/framework/console/controllers/HelpController.php +++ b/framework/console/controllers/HelpController.php @@ -532,7 +532,7 @@ class HelpController extends Controller protected function formatOptionAliases($controller, $option) { foreach ($controller->optionAliases() as $name => $value) { - if ($value === $option) { + if (Inflector::camel2id($value, '-', true) === $option) { return ', -' . $name; } } diff --git a/tests/framework/console/ControllerTest.php b/tests/framework/console/ControllerTest.php index 6f685e5d01..4292c0bb04 100644 --- a/tests/framework/console/ControllerTest.php +++ b/tests/framework/console/ControllerTest.php @@ -23,6 +23,7 @@ class ControllerTest extends TestCase $this->mockApplication(); Yii::$app->controllerMap = [ 'fake' => 'yiiunit\framework\console\FakeController', + 'fake_witout_output' => 'yiiunit\framework\console\FakeHelpControllerWithoutOutput', 'help' => 'yiiunit\framework\console\FakeHelpController', ]; } @@ -127,6 +128,10 @@ class ControllerTest extends TestCase $this->assertFalse(FakeController::getWasActionIndexCalled()); $this->assertEquals(FakeHelpController::getActionIndexLastCallParams(), ['posts/index']); + + $helpController = new FakeHelpControllerWithoutOutput('help', Yii::$app); + $helpController->actionIndex('fake/aksi1'); + $this->assertContains('--test-array, -ta', $helpController->outputString); } /** diff --git a/tests/framework/console/FakeHelpControllerWithoutOutput.php b/tests/framework/console/FakeHelpControllerWithoutOutput.php new file mode 100644 index 0000000000..fbd581adf2 --- /dev/null +++ b/tests/framework/console/FakeHelpControllerWithoutOutput.php @@ -0,0 +1,21 @@ +outputString .= $string; + } +} From a8d4f8538e6996a5b1153e714bb7b14e210b30cd Mon Sep 17 00:00:00 2001 From: Nikolay Poryadin Date: Sat, 9 Mar 2019 15:39:58 +0300 Subject: [PATCH 3/5] Fixes #16681: `ActiveField::inputOptions` were not used during some widgets rendering --- framework/CHANGELOG.md | 1 + framework/widgets/ActiveField.php | 5 ++ tests/framework/widgets/ActiveFieldTest.php | 53 +++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index c3dbc394d4..4243face3d 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.17 under development ------------------------ +- Bug #16681: `ActiveField::inputOptions` were not used during some widgets rendering (GHopperMSK) - Bug #17133: Fixed aliases rendering during help generation for a console command (GHopperMSK) - Bug #17185: Fixed `AssetManager` timestamp appending when a file is published manually (GHopperMSK) - Bug #17156: Fixes PHP 7.2 warning when a data provider has no data as a parameter for a GridView (evilito) diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index cf23b8c007..c1ede6fd30 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -763,6 +763,11 @@ class ActiveField extends Component */ public function widget($class, $config = []) { + foreach ($this->inputOptions as $key => $value) { + if (!isset($config['options'][$key])) { + $config['options'][$key] = $value; + } + } /* @var $class \yii\base\Widget */ $config['model'] = $this->model; $config['attribute'] = $this->attribute; diff --git a/tests/framework/widgets/ActiveFieldTest.php b/tests/framework/widgets/ActiveFieldTest.php index 9b3d32b5ec..a751801089 100644 --- a/tests/framework/widgets/ActiveFieldTest.php +++ b/tests/framework/widgets/ActiveFieldTest.php @@ -14,6 +14,7 @@ use yii\web\View; use yii\widgets\ActiveField; use yii\widgets\ActiveForm; use yii\widgets\InputWidget; +use yii\widgets\MaskedInput; /** * @author Nelson J Morais @@ -584,6 +585,30 @@ HTML; $this->assertEqualsWithoutLE($expectedValue, trim($actualValue)); } + public function testInputOptionsTransferToWidget() + { + $widget = $this->activeField->widget(TestMaskedInput::className(), [ + 'mask' => '999-999-9999', + 'options' => ['placeholder' => 'pholder_direct'], + ]); + $this->assertContains('placeholder="pholder_direct"', (string) $widget); + + // transfer options from ActiveField to widget + $this->activeField->inputOptions = ['placeholder' => 'pholder_input']; + $widget = $this->activeField->widget(TestMaskedInput::className(), [ + 'mask' => '999-999-9999', + ]); + $this->assertContains('placeholder="pholder_input"', (string) $widget); + + // set both AF and widget options (second one takes precedence) + $this->activeField->inputOptions = ['placeholder' => 'pholder_both_input']; + $widget = $this->activeField->widget(TestMaskedInput::className(), [ + 'mask' => '999-999-9999', + 'options' => ['placeholder' => 'pholder_both_direct'] + ]); + $this->assertContains('placeholder="pholder_both_direct"', (string) $widget); + } + /** * Helper methods. */ @@ -667,3 +692,31 @@ class TestInputWidget extends InputWidget return 'Render: ' . get_class($this); } } + +class TestMaskedInput extends MaskedInput +{ + /** + * @var static + */ + public static $lastInstance; + + public function init() + { + parent::init(); + self::$lastInstance = $this; + } + + public function getOptions() { + return $this->options; + } + + public function run() + { + return 'Options: ' . implode(', ', array_map( + function ($v, $k) { return sprintf('%s="%s"', $k, $v); }, + $this->options, + array_keys($this->options) + )); + } +} + From 8bb334b9ae5760f5ad3548f449bf4fe5b9d98d22 Mon Sep 17 00:00:00 2001 From: lubosdz Date: Sat, 9 Mar 2019 13:54:39 +0100 Subject: [PATCH 4/5] Fixes #9438, #13740, #15037: Handle DB session callback custom fields before session closed --- framework/CHANGELOG.md | 1 + framework/web/DbSession.php | 40 +++++++++++++++++-- framework/web/MultiFieldSession.php | 29 +++++--------- .../web/session/AbstractDbSessionTest.php | 28 +++++++++++++ 4 files changed, 74 insertions(+), 24 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 4243face3d..a2b6f351e7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.17 under development ------------------------ +- Bug #9438, #13740, #15037: Handle DB session callback custom fields before session closed (lubosdz) - Bug #16681: `ActiveField::inputOptions` were not used during some widgets rendering (GHopperMSK) - Bug #17133: Fixed aliases rendering during help generation for a console command (GHopperMSK) - Bug #17185: Fixed `AssetManager` timestamp appending when a file is published manually (GHopperMSK) diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php index 7bd63e98bd..b00df3be04 100644 --- a/framework/web/DbSession.php +++ b/framework/web/DbSession.php @@ -76,6 +76,11 @@ class DbSession extends MultiFieldSession */ public $sessionTable = '{{%session}}'; + /** + * @var array Session fields to be written into session table columns + * @since 2.0.17 + */ + protected $fields = []; /** * Initializes the DbSession component. @@ -136,6 +141,19 @@ class DbSession extends MultiFieldSession } } + /** + * Ends the current session and store session data. + * @since 2.0.17 + */ + public function close() + { + if ($this->getIsActive()) { + // prepare writeCallback fields before session closes + $this->fields = $this->composeFields(); + YII_DEBUG ? session_write_close() : @session_write_close(); + } + } + /** * Session read handler. * @internal Do not call this method directly. @@ -169,14 +187,28 @@ class DbSession extends MultiFieldSession // exception must be caught in session write handler // https://secure.php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes try { - $fields = $this->composeFields($id, $data); - $fields = $this->typecastFields($fields); - $this->db->createCommand()->upsert($this->sessionTable, $fields)->execute(); + // ensure backwards compatability (fixed #9438) + if ($this->writeCallback && !$this->fields) { + $this->fields = $this->composeFields(); + } + // ensure data consistency + if (!isset($this->fields['data'])) { + $this->fields['data'] = $data; + } else { + $_SESSION = $this->fields['data']; + } + // ensure 'id' and 'expire' are never affected by [[writeCallback]] + $this->fields = array_merge($this->fields, [ + 'id' => $id, + 'expire' => time() + $this->getTimeout(), + ]); + $this->fields = $this->typecastFields($this->fields); + $this->db->createCommand()->upsert($this->sessionTable, $this->fields)->execute(); + $this->fields = []; } catch (\Exception $e) { Yii::$app->errorHandler->handleException($e); return false; } - return true; } diff --git a/framework/web/MultiFieldSession.php b/framework/web/MultiFieldSession.php index 56ff24037f..cd26d120ac 100644 --- a/framework/web/MultiFieldSession.php +++ b/framework/web/MultiFieldSession.php @@ -89,30 +89,19 @@ abstract class MultiFieldSession extends Session /** * Composes storage field set for session writing. - * @param string $id session id - * @param string $data session data + * @param string $id Optional session id + * @param string $data Optional session data * @return array storage fields */ - protected function composeFields($id, $data) + protected function composeFields($id = null, $data = null) { - $fields = [ - 'data' => $data, - ]; - if ($this->writeCallback !== null) { - $fields = array_merge( - $fields, - call_user_func($this->writeCallback, $this) - ); - if (!is_string($fields['data'])) { - $_SESSION = $fields['data']; - $fields['data'] = session_encode(); - } + $fields = $this->writeCallback ? call_user_func($this->writeCallback, $this) : []; + if ($id !== null) { + $fields['id'] = $id; + } + if ($data !== null) { + $fields['data'] = $data; } - // ensure 'id' and 'expire' are never affected by [[writeCallback]] - $fields = array_merge($fields, [ - 'id' => $id, - 'expire' => time() + $this->getTimeout(), - ]); return $fields; } diff --git a/tests/framework/web/session/AbstractDbSessionTest.php b/tests/framework/web/session/AbstractDbSessionTest.php index a215e81d0e..a2a4375c29 100644 --- a/tests/framework/web/session/AbstractDbSessionTest.php +++ b/tests/framework/web/session/AbstractDbSessionTest.php @@ -10,6 +10,7 @@ namespace yiiunit\framework\web\session; use Yii; use yii\db\Connection; use yii\db\Query; +use yii\db\Migration; use yii\web\DbSession; use yiiunit\framework\console\controllers\EchoMigrateController; use yiiunit\TestCase; @@ -147,6 +148,33 @@ abstract class AbstractDbSessionTest extends TestCase $this->assertSame('changed by callback data', $session->readSession('test')); } + /** + * @depends testReadWrite + */ + public function testWriteCustomFieldWithUserId() + { + $session = new DbSession(); + $session->open(); + $session->set('user_id', 12345); + + // add mapped custom column + $migration = new Migration; + $migration->addColumn($session->sessionTable, 'user_id', $migration->integer()); + + $session->writeCallback = function ($session) { + return ['user_id' => $session['user_id']]; + }; + + // here used to be error, fixed issue #9438 + $session->close(); + + // reopen & read session from DB + $session->open(); + $loadedUserId = empty($session['user_id']) ? null : $session['user_id']; + $this->assertSame($loadedUserId, 12345); + $session->close(); + } + protected function buildObjectForSerialization() { $object = new \stdClass(); From 75358f1f1855149bb981ab019477af1700dac3b0 Mon Sep 17 00:00:00 2001 From: Evgeniy Moiseenko Date: Mon, 11 Mar 2019 12:24:33 +0300 Subject: [PATCH 5/5] Added JSON AR translation to Russian docs (#17199) [skip ci] --- docs/guide-ru/db-active-record.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/guide-ru/db-active-record.md b/docs/guide-ru/db-active-record.md index 8ce03ec0ea..94015fec1b 100644 --- a/docs/guide-ru/db-active-record.md +++ b/docs/guide-ru/db-active-record.md @@ -521,7 +521,25 @@ $customer->loadDefaultValues(); > Совет: вы можете использовать поведение [[yii\behaviors\AttributeTypecastBehavior]] для того, чтобы производить приведение типов для ActiveRecord во время валидации или сохранения. + +Начиная с 2.0.14, Yii ActiveRecord поддерживает сложные типы данных, такие как JSON или многомерные массивы. +#### JSON в MySQL и PostgreSQL +После заполнения данных, значение из столбца JSON будет автоматически декодировано из JSON в соответствии со стандартными правилами декодирования JSON. + +Чтобы сохранить значение атрибута в столбец JSON, ActiveRecord автоматически создаст объект [[yii\db\JsonExpression|JsonExpression]], который будет закодирован в строку JSON на уровне [QueryBuilder](db-query-builder.md). + +#### Массивы в PostgreSQL +После заполнения данных значение из столбца `Array` будет автоматически декодировано из нотации PgSQL в объект [[yii\db\ArrayExpression|ArrayExpression]]. Он реализует интерфейс PHP `ArrayAccess`, так что вы можете использовать его в качестве массива, или вызвать `->getValue ()`, чтобы получить сам массив. + +Чтобы сохранить значение атрибута в столбец массива, ActiveRecord автоматически создаст объект [[yii\db\Array Expression|ArrayExpression]], который будет закодирован [QueryBuilder](db-query-builder.md) в строковое представление массива PgSQL. + +Можно также использовать условия для столбцов JSON: + +```php +$query->andWhere(['=', 'json', new ArrayExpression(['foo' => 'bar']) +``` +Дополнительные сведения о системе построения выражений см. [Query Builder – добавление пользовательских условий и выражений](db-query-builder.md#adding-custom-conditions-and-expressions) ### Обновление нескольких строк данных