From 25059c4e0880bedd705c65b3329c658b6efe85a4 Mon Sep 17 00:00:00 2001
From: 101000june <39669835+101000june@users.noreply.github.com>
Date: Sun, 27 Oct 2024 11:08:06 +0200
Subject: [PATCH 1/2] =?UTF-8?q?Docs:=20Ukrainian=20=E2=80=94=20Add=20Sessi?=
=?UTF-8?q?ons&Cookies=20page=20(#20272)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/guide-uk/runtime-sessions-cookies.md | 285 ++++++++++++++++++++++
1 file changed, 285 insertions(+)
create mode 100644 docs/guide-uk/runtime-sessions-cookies.md
diff --git a/docs/guide-uk/runtime-sessions-cookies.md b/docs/guide-uk/runtime-sessions-cookies.md
new file mode 100644
index 00000000000..7edf8e00f8b
--- /dev/null
+++ b/docs/guide-uk/runtime-sessions-cookies.md
@@ -0,0 +1,285 @@
+Сесії та кукі
+====================
+
+Сесії та кукі дозволяють зберігати користувацькі дані між запитами. При використанні чистого PHP можна отримати доступ до цих даних через глобальні змінні `$_SESSION` та `$_COOKIE`, відповідно. Yii інкапсулює сесії та кукі в об'єкти, що дає можливість звертатися до них в об'єктноорієнтованому стилі та забезпечує додаткову зручність в роботі.
+
+
+## Сесії
+
+За аналогією з [запитами](runtime-requests.md) та [відповідями](runtime-responses.md), до сесій можна отримати доступ через `session` [компонент додатка](structure-application-components.md), який за замовчуванням є екземпляром [[yii\web\Session]].
+
+
+### Відкриття та закриття сесії
+
+Відкрити та закрити сесію можна наступним чином:
+
+```php
+$session = Yii::$app->session;
+
+// перевіряєм що сесія вже відкрита
+if ($session->isActive) ...
+
+// відкиваєм сесію
+$session->open();
+
+// закриваємо сесію
+$session->close();
+
+// знищуємо сесію і всі пов'язані з нею дані.
+$session->destroy();
+```
+
+Можна викликати [[yii\web\Session::open()|open()]] і [[yii\web\Session::close()|close()]] багаторазово без виникнення помилок; всередині компонента всі методи перевіряють сесію на те, відкрита вона чи ні.
+
+
+### Доступ до даних сесії
+
+Отримати доступ до збережених в сесію даних можна наступним чином:
+
+```php
+$session = Yii::$app->session;
+
+// отримання змінної з сесії. Наступні способи використання еквівалентні:
+$language = $session->get('language');
+$language = $session['language'];
+$language = isset($_SESSION['language']) ? $_SESSION['language'] : null;
+
+// запис змінної в сесію. Наступні способи використання еквівалентні:
+$session->set('language', 'en-US');
+$session['language'] = 'en-US';
+$_SESSION['language'] = 'en-US';
+
+// видалення змінної з сесії. Наступні способи використання еквівалентні:
+$session->remove('language');
+unset($session['language']);
+unset($_SESSION['language']);
+
+// перевірка на існування змінної в сесії. Наступні способи використання еквівалентні:
+if ($session->has('language')) ...
+if (isset($session['language'])) ...
+if (isset($_SESSION['language'])) ...
+
+// обхід усіх змінних у сесії. Наступні способи використання еквівалентні:
+foreach ($session as $name => $value) ...
+foreach ($_SESSION as $name => $value) ...
+```
+
+> Info: При отриманні даних з сесії через компонент `session`, сесія буде автоматично відкрита, якщо вона не була відкрита до цього. У цьому полягає відмінність від отримання даних з глобальної змінної `$_SESSION`, що вимагає обов'язкового виклику `session_start()`.
+
+При роботі з сесійними даними, які є масивами, компонент `session` має обмеження, що забороняє пряму модифікацію окремих елементів масиву. Наприклад,
+
+```php
+$session = Yii::$app->session;
+
+// наступний код НЕ БУДЕ працювати
+$session['captcha']['number'] = 5;
+$session['captcha']['lifetime'] = 3600;
+
+// а цей буде:
+$session['captcha'] = [
+ 'number' => 5,
+ 'lifetime' => 3600,
+];
+
+// цей код також буде працювати:
+echo $session['captcha']['lifetime'];
+```
+
+Для вирішення цієї проблеми можна використовувати такі обхідні прийоми:
+
+```php
+$session = Yii::$app->session;
+
+// пряме використання $_SESSION (переконайтеся, що Yii::$app->session->open() був викликаний)
+$_SESSION['captcha']['number'] = 5;
+$_SESSION['captcha']['lifetime'] = 3600;
+
+// отримайте весь масив, модифікуйте і збережіть назад у сесію
+$captcha = $session['captcha'];
+$captcha['number'] = 5;
+$captcha['lifetime'] = 3600;
+$session['captcha'] = $captcha;
+
+// використовуйте ArrayObject замість масиву
+$session['captcha'] = new \ArrayObject;
+...
+$session['captcha']['number'] = 5;
+$session['captcha']['lifetime'] = 3600;
+
+// записуйте дані з ключами, які мають однаковий префікс
+$session['captcha.number'] = 5;
+$session['captcha.lifetime'] = 3600;
+```
+
+Для покращення продуктивності та читабельності коду рекомендується використовувати останній прийом. Іншими словами, замість того, щоб зберігати масив як одну змінну сесії, ми зберігаємо кожен елемент масиву як звичайну сесійну змінну зі спільним префіксом.
+
+
+### Користувацьке сховище для сесії
+
+За замовчуванням клас [[yii\web\Session]] зберігає дані сесії у вигляді файлів на сервері. Однак Yii надає ряд класів, які реалізують різні способи зберігання даних сесії:
+
+* [[yii\web\DbSession]]: зберігає дані сесії в базі даних.
+* [[yii\web\CacheSession]]: зберігання даних сесії в попередньо сконфігурованому компоненті кешу [кеш](caching-data.md#cache-components).
+* [[yii\redis\Session]]: зберігання даних сесії в [redis](https://redis.io/).
+* [[yii\mongodb\Session]]: зберігання сесії в [MongoDB](https://www.mongodb.com/).
+
+Усі ці класи підтримують однаковий набір методів API. В результаті ви можете перемикатися між різними сховищами сесій без модифікації коду додатку.
+
+> Note: Якщо ви хочете отримати дані з змінної `$_SESSION` при використанні користувацького сховища, ви повинні бути впевнені, що сесія вже стартувала [[yii\web\Session::open()]], оскільки обробники зберігання користувацьких сесій реєструються в цьому методі.
+
+Щоб дізнатися, як налаштувати і використовувати ці компоненти, зверніться до документації по API. Нижче наведено приклад конфігурації [[yii\web\DbSession]] для використання бази даних для зберігання сесії:
+
+```php
+return [
+ 'components' => [
+ 'session' => [
+ 'class' => 'yii\web\DbSession',
+ // 'db' => 'mydb', // ID компонента для взаємодії з БД. По замовчуванню 'db'.
+ // 'sessionTable' => 'my_session', // назва таблиці для даних сесії. По замовчуванню 'session'.
+ ],
+ ],
+];
+```
+
+Також необхідно створити таблицю для зберігання даних сесії:
+
+```sql
+CREATE TABLE session
+(
+ id CHAR(40) NOT NULL PRIMARY KEY,
+ expire INTEGER,
+ data BLOB
+)
+```
+
+де 'BLOB' відповідає типу даних вашої DBMS. Нижче наведені приклади відповідності типів BLOB у найбільш популярних DBMS:
+
+- MySQL: LONGBLOB
+- PostgreSQL: BYTEA
+- MSSQL: BLOB
+
+> Note: В залежності від налаштувань параметра `session.hash_function` у вашому php.ini, може знадобитися змінити довжину поля `id`. Наприклад, якщо `session.hash_function=sha256`, потрібно встановити довжину поля на 64 замість 40.
+
+### Flash-повідомлення
+
+Flash-повідомлення - це особливий тип даних у сесії, які встановлюються один раз під час запиту і доступні лише протягом наступного запиту, після чого вони автоматично видаляються. Такий спосіб зберігання інформації в сесії найчастіше використовується для реалізації повідомлень, які будуть відображені кінцевому користувачу один раз, наприклад, підтвердження про успішну відправку форми.
+
+Встановити та отримати flash-повідомлення можна через компонент програми `session`. Наприклад:
+
+```php
+$session = Yii::$app->session;
+
+// Запит #1
+// встановлення flash-повідомлення з назвою "postDeleted"
+$session->setFlash('postDeleted', 'Ви успішно видалили пост.');
+
+// Запит #2
+// відображення flash-повідомлення "postDeleted"
+echo $session->getFlash('postDeleted');
+
+// Запит #3
+// змінна $result буде мати значення false, оскільки flash-повідомлення було автоматично видалено
+$result = $session->hasFlash('postDeleted');
+```
+
+Оскільки flash-повідомлення зберігаються в сесії як звичайні дані, в них можна записувати довільну інформацію, і вона буде доступна лише в наступному запиті.
+
+При виклику [[yii\web\Session::setFlash()]] відбувається перезаписування flash-повідомлень з таким же назвою. Для того, щоб додати нові дані до вже існуючого flash-повідомлення, необхідно викликати [[yii\web\Session::addFlash()]].
+Наприклад:
+
+```php
+$session = Yii::$app->session;
+
+// Запит #1
+// додати нове flash-повідомлення з назвою "alerts"
+$session->addFlash('alerts', 'Ви успішно видалили пост.');
+$session->addFlash('alerts', 'Ви успішно додали нового друга.');
+$session->addFlash('alerts', 'Дякуємо.');
+
+// Запит #2
+// Змінна $alerts тепер містить масив flash-повідомлень з назвою "alerts"
+$alerts = $session->getFlash('alerts');
+```
+
+> Note: Намагайтеся не використовувати [[yii\web\Session::setFlash()]] спільно з [[yii\web\Session::addFlash()]] для flash-повідомлень з однаковою назвою. Це пов'язано з тим, що останній метод автоматично перетворює збережені дані в масив, щоб мати можливість зберігати та додавати нові дані в flash-повідомлення з тією ж назвою. В результаті, при виклику [[yii\web\Session::getFlash()]] можна виявити, що повертається масив, тоді як очікувалася строка.
+
+## Кукі
+
+Yii представляє кожну з cookie як об'єкт [[yii\web\Cookie]]. Обидва компоненти програми [[yii\web\Request]] і [[yii\web\Response]]
+підтримують колекції кукі через своє властивість cookies. У першому випадку колекція кукі є їх представленням з HTTP-запиту, у другому — представляє кукі, які будуть відправлені користувачу.
+
+### Читання кукі
+
+Отримати кукі з поточного запиту можна наступним чином:
+
+```php
+// отримання колекції кукі (yii\web\CookieCollection) з компонента "request"
+$cookies = Yii::$app->request->cookies;
+
+// отримання кукі з назвою "language". Якщо кукі не існує, "en" буде повернуто як значення за замовчуванням.
+$language = $cookies->getValue('language', 'en');
+
+// альтернативний спосіб отримання кукі "language"
+if (($cookie = $cookies->get('language')) !== null) {
+ $language = $cookie->value;
+}
+
+// тепер змінну $cookies можна використовувати як масив
+if (isset($cookies['language'])) {
+ $language = $cookies['language']->value;
+}
+
+// перевірка на існування кукі "language"
+if ($cookies->has('language')) ...
+if (isset($cookies['language'])) ...
+```
+
+
+### Відправка кукі
+
+Відправити кукі кінцевому користувачу можна наступним чином:
+
+```php
+// отримання колекції (yii\web\CookieCollection) з компонента "response"
+$cookies = Yii::$app->response->cookies;
+
+// додавання нової кукі в HTTP-відповідь
+$cookies->add(new \yii\web\Cookie([
+ 'name' => 'language',
+ 'value' => 'zh-CN',
+]));
+
+// видалення кукі...
+$cookies->remove('language');
+// ...що еквівалентно наступному:
+unset($cookies['language']);
+```
+
+Крім властивостей [[yii\web\Cookie::name|name]] та [[yii\web\Cookie::value|value]], клас [[yii\web\Cookie]] також надає ряд властивостей для отримання інформації про куки: [[yii\web\Cookie::domain|domain]], [[yii\web\Cookie::expire|expire]]. Ці властивості можна сконфігурувати, а потім додати кукі в колекцію для HTTP-відповіді.
+
+> Note: Для більшої безпеки значення властивості [[yii\web\Cookie::httpOnly]] за замовчуванням встановлено в `true`. Це зменшує ризики доступу до захищеної кукі на клієнтській стороні (якщо браузер підтримує таку можливість). Ви можете звернутися до [httpOnly wiki](https://owasp.org/www-community/HttpOnly) для додаткової інформації.
+
+### Валідація кукі
+
+Під час запису та читання куків через компоненти `request` та `response`, як буде показано в двох наступних підрозділах, фреймворк надає автоматичну валідацію, яка забезпечує захист кукі від модифікації на стороні клієнта. Це досягається завдяки підписанню кожної кукі секретним ключем, що дозволяє додатку розпізнавати кукі, які були модифіковані на клієнтській стороні. У такому випадку кукі НЕ БУДЕ доступна через властивість [[yii\web\Request::cookies|cookie collection]] компонента `request`.
+
+> Note: Валідація кукі захищає тільки від їх модифікації. Якщо валідація не була пройдена, отримати доступ до кукі все ще можна через глобальну змінну `$_COOKIE`. Це пов'язано з тим, що додаткові пакети та бібліотеки можуть маніпулювати кукі без виклику валідації, яку забезпечує Yii.
+
+
+За замовчуванням валідація кукі увімкнена. Її можна вимкнути, встановивши властивість [[yii\web\Request::enableCookieValidation]] в `false`, однак ми настійливо не рекомендуємо цього робити.
+
+> Note: Кукі, які безпосередньо читаються/пишуться через `$_COOKIE` та `setcookie()`, НЕ БУДУТЬ валідовуватися.
+
+При використанні валідації кукі необхідно вказати значення властивості [[yii\web\Request::cookieValidationKey]], яке буде використано для генерації згаданого вище секретного ключа. Це можна зробити, налаштувавши компонент `request` у конфігурації додатка:
+
+```php
+return [
+ 'components' => [
+ 'request' => [
+ 'cookieValidationKey' => 'fill in a secret key here',
+ ],
+ ],
+];
+```
+
+> Note: Властивість [[yii\web\Request::cookieValidationKey|cookieValidationKey]] є секретним значенням і повинно бути відомо лише тим, кому ви довіряєте. Не розміщуйте цю інформацію в системі контролю версій.
From e4d5d73490bc4d382f2646573d0e1edc2be8461a Mon Sep 17 00:00:00 2001
From: Christina Reichel <123160582+chriscpty@users.noreply.github.com>
Date: Sun, 27 Oct 2024 11:37:09 +0100
Subject: [PATCH 2/2] Fix #20268: Minor optimisation in
`\yii\helpers\BaseArrayHelper::map`
---
framework/CHANGELOG.md | 1 +
framework/helpers/BaseArrayHelper.php | 3 ++
framework/helpers/BaseStringHelper.php | 33 +++++++------
tests/framework/helpers/ArrayHelperTest.php | 53 ++++++++++++++++++++-
4 files changed, 75 insertions(+), 15 deletions(-)
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index 294810f9fc7..1935d097811 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -9,6 +9,7 @@ Yii Framework 2 Change Log
- Enh #20247: Support for variadic console controller action methods (brandonkelly)
- Bug #20256: Add support for dropping views in MSSQL server when running migrate/fresh (ambrozt)
- Enh #20248: Add support for attaching behaviors in configurations with Closure (timkelty)
+- Enh #20268: Minor optimisation in `\yii\helpers\BaseArrayHelper::map` (chriscpty)
2.0.51 July 18, 2024
--------------------
diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php
index 56411163e1e..bc770f96cb7 100644
--- a/framework/helpers/BaseArrayHelper.php
+++ b/framework/helpers/BaseArrayHelper.php
@@ -595,6 +595,9 @@ public static function getColumn($array, $name, $keepKeys = true)
*/
public static function map($array, $from, $to, $group = null)
{
+ if (is_string($from) && is_string($to) && $group === null && strpos($from, '.') === false && strpos($to, '.') === false) {
+ return array_column($array, $to, $from);
+ }
$result = [];
foreach ($array as $element) {
$key = static::getValue($element, $from);
diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php
index ec9252aa4c4..5854e29766d 100644
--- a/framework/helpers/BaseStringHelper.php
+++ b/framework/helpers/BaseStringHelper.php
@@ -313,9 +313,14 @@ public static function explode($string, $delimiter = ',', $trim = true, $skipEmp
}
if ($skipEmpty) {
// Wrapped with array_values to make array keys sequential after empty values removing
- $result = array_values(array_filter($result, function ($value) {
- return $value !== '';
- }));
+ $result = array_values(
+ array_filter(
+ $result,
+ function ($value) {
+ return $value !== '';
+ }
+ )
+ );
}
return $result;
@@ -343,7 +348,7 @@ public static function countWords($string)
*/
public static function normalizeNumber($value)
{
- $value = (string) $value;
+ $value = (string)$value;
$localeInfo = localeconv();
$decimalSeparator = isset($localeInfo['decimal_point']) ? $localeInfo['decimal_point'] : null;
@@ -396,7 +401,7 @@ public static function floatToString($number)
{
// . and , are the only decimal separators known in ICU data,
// so its safe to call str_replace here
- return str_replace(',', '.', (string) $number);
+ return str_replace(',', '.', (string)$number);
}
/**
@@ -422,14 +427,14 @@ public static function matchWildcard($pattern, $string, $options = [])
$replacements = [
'\\\\\\\\' => '\\\\',
- '\\\\\\*' => '[*]',
- '\\\\\\?' => '[?]',
- '\*' => '.*',
- '\?' => '.',
- '\[\!' => '[^',
- '\[' => '[',
- '\]' => ']',
- '\-' => '-',
+ '\\\\\\*' => '[*]',
+ '\\\\\\?' => '[?]',
+ '\*' => '.*',
+ '\?' => '.',
+ '\[\!' => '[^',
+ '\[' => '[',
+ '\]' => ']',
+ '\-' => '-',
];
if (isset($options['escape']) && !$options['escape']) {
@@ -483,7 +488,7 @@ public static function mb_ucfirst($string, $encoding = 'UTF-8')
*/
public static function mb_ucwords($string, $encoding = 'UTF-8')
{
- $string = (string) $string;
+ $string = (string)$string;
if (empty($string)) {
return $string;
}
diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php
index f450b281f7a..a086503006b 100644
--- a/tests/framework/helpers/ArrayHelperTest.php
+++ b/tests/framework/helpers/ArrayHelperTest.php
@@ -734,6 +734,57 @@ public function testMap()
'345' => 'ccc',
],
], $result);
+
+ $result = ArrayHelper::map($array,
+ static function (array $group) {
+ return $group['id'] . $group['name'];
+ },
+ static function (array $group) {
+ return $group['name'] . $group['class'];
+ }
+ );
+
+ $this->assertEquals([
+ '123aaa' => 'aaax',
+ '124bbb' => 'bbbx',
+ '345ccc' => 'cccy',
+ ], $result);
+
+ $result = ArrayHelper::map($array,
+ static function (array $group) {
+ return $group['id'] . $group['name'];
+ },
+ static function (array $group) {
+ return $group['name'] . $group['class'];
+ },
+ static function (array $group) {
+ return $group['class'] . '-' . $group['class'];
+ }
+ );
+
+ $this->assertEquals([
+ 'x-x' => [
+ '123aaa' => 'aaax',
+ '124bbb' => 'bbbx',
+ ],
+ 'y-y' => [
+ '345ccc' => 'cccy',
+ ],
+ ], $result);
+
+ $array = [
+ ['id' => '123', 'name' => 'aaa', 'class' => 'x', 'map' => ['a' => '11', 'b' => '22']],
+ ['id' => '124', 'name' => 'bbb', 'class' => 'x', 'map' => ['a' => '33', 'b' => '44']],
+ ['id' => '345', 'name' => 'ccc', 'class' => 'y', 'map' => ['a' => '55', 'b' => '66']],
+ ];
+
+ $result = ArrayHelper::map($array, 'map.a', 'map.b');
+
+ $this->assertEquals([
+ '11' => '22',
+ '33' => '44',
+ '55' => '66'
+ ], $result);
}
public function testKeyExists()
@@ -759,7 +810,7 @@ public function testKeyExistsWithFloat()
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
$this->markTestSkipped('Using floats as array key is deprecated.');
}
-
+
$array = [
1 => 3,
2.2 => 4, // Note: Floats are cast to ints, which means that the fractional part will be truncated.