mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-01 20:19:42 +08:00
Merge branch 'master' into 13920-validation-marks-valid-field-as-invalid
This commit is contained in:
@ -373,7 +373,7 @@ $query->orderBy([
|
|||||||
```
|
```
|
||||||
|
|
||||||
В данном коде, ключи массива - это имена столбцов, а значения массива - это соответствующее направление сортировки.
|
В данном коде, ключи массива - это имена столбцов, а значения массива - это соответствующее направление сортировки.
|
||||||
PHP константа `SORT_ASC` определяет сортировку по возрастанию и `SORT_DESC` сортировка по умолчанию.
|
PHP константа `SORT_ASC` определяет сортировку по возрастанию и `SORT_DESC` сортировку по убыванию.
|
||||||
|
|
||||||
Если `ORDER BY` содержит только простые имена столбцов, вы можете определить их с помощью столбцов, также
|
Если `ORDER BY` содержит только простые имена столбцов, вы можете определить их с помощью столбцов, также
|
||||||
как и при написании обычного SQL. Например,
|
как и при написании обычного SQL. Например,
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class BaseYii
|
|||||||
*/
|
*/
|
||||||
public static $classMap = [];
|
public static $classMap = [];
|
||||||
/**
|
/**
|
||||||
* @var \yii\console\Application|\yii\web\Application|\yii\base\Application the application instance
|
* @var \yii\console\Application|\yii\web\Application the application instance
|
||||||
*/
|
*/
|
||||||
public static $app;
|
public static $app;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,6 +5,7 @@ Yii Framework 2 Change Log
|
|||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
- Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher)
|
- Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher)
|
||||||
|
- Bug #19872: Fixed the definition of dirty attributes in AR properties for a non-associative array in case of changing the order of elements (eegusakov)
|
||||||
- Bug #19899: Fixed `GridView` in some cases calling `Model::generateAttributeLabel()` to generate label values that are never used (PowerGamer1)
|
- Bug #19899: Fixed `GridView` in some cases calling `Model::generateAttributeLabel()` to generate label values that are never used (PowerGamer1)
|
||||||
- Bug #9899: Fix caching a MSSQL query with BLOB data type (terabytesoftw)
|
- Bug #9899: Fix caching a MSSQL query with BLOB data type (terabytesoftw)
|
||||||
- Bug #16208: Fix `yii\log\FileTarget` to not export empty messages (terabytesoftw)
|
- Bug #16208: Fix `yii\log\FileTarget` to not export empty messages (terabytesoftw)
|
||||||
@ -16,6 +17,8 @@ Yii Framework 2 Change Log
|
|||||||
- Enh #19884: Added support Enums in Query Builder (sk1t0n)
|
- Enh #19884: Added support Enums in Query Builder (sk1t0n)
|
||||||
- Bug #19908: Fix associative array cell content rendering in Table widget (rhertogh)
|
- Bug #19908: Fix associative array cell content rendering in Table widget (rhertogh)
|
||||||
- Bug #19906: Fixed multiline strings in the `\yii\console\widgets\Table` widget (rhertogh)
|
- Bug #19906: Fixed multiline strings in the `\yii\console\widgets\Table` widget (rhertogh)
|
||||||
|
- Bug #19914: Fixed `ArrayHelper::keyExists()` and `::remove()` functions when the key is a float and the value is `null` (rhertogh)
|
||||||
|
- Enh #19920: Broadened the accepted type of `Cookie::$expire` from `int` to `int|string|\DateTimeInterface|null` (rhertogh)
|
||||||
|
|
||||||
|
|
||||||
2.0.48.1 May 24, 2023
|
2.0.48.1 May 24, 2023
|
||||||
|
|||||||
@ -282,7 +282,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
|||||||
*/
|
*/
|
||||||
public function __get($name)
|
public function __get($name)
|
||||||
{
|
{
|
||||||
if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
|
if (array_key_exists($name, $this->_attributes)) {
|
||||||
return $this->_attributes[$name];
|
return $this->_attributes[$name];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
|
if (array_key_exists($name, $this->_related)) {
|
||||||
return $this->_related[$name];
|
return $this->_related[$name];
|
||||||
}
|
}
|
||||||
$value = parent::__get($name);
|
$value = parent::__get($name);
|
||||||
@ -1774,7 +1774,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
|||||||
*/
|
*/
|
||||||
private function isValueDifferent($newValue, $oldValue)
|
private function isValueDifferent($newValue, $oldValue)
|
||||||
{
|
{
|
||||||
if (is_array($newValue) && is_array($oldValue) && !ArrayHelper::isAssociative($oldValue)) {
|
if (is_array($newValue) && is_array($oldValue) && ArrayHelper::isAssociative($oldValue)) {
|
||||||
$newValue = ArrayHelper::recursiveSort($newValue);
|
$newValue = ArrayHelper::recursiveSort($newValue);
|
||||||
$oldValue = ArrayHelper::recursiveSort($oldValue);
|
$oldValue = ArrayHelper::recursiveSort($oldValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -327,7 +327,12 @@ class BaseArrayHelper
|
|||||||
*/
|
*/
|
||||||
public static function remove(&$array, $key, $default = null)
|
public static function remove(&$array, $key, $default = null)
|
||||||
{
|
{
|
||||||
if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
|
// ToDo: This check can be removed when the minimum PHP version is >= 8.1 (Yii2.2)
|
||||||
|
if (is_float($key)) {
|
||||||
|
$key = (int)$key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($array) && array_key_exists($key, $array)) {
|
||||||
$value = $array[$key];
|
$value = $array[$key];
|
||||||
unset($array[$key]);
|
unset($array[$key]);
|
||||||
|
|
||||||
@ -608,17 +613,20 @@ class BaseArrayHelper
|
|||||||
* Checks if the given array contains the specified key.
|
* Checks if the given array contains the specified key.
|
||||||
* This method enhances the `array_key_exists()` function by supporting case-insensitive
|
* This method enhances the `array_key_exists()` function by supporting case-insensitive
|
||||||
* key comparison.
|
* key comparison.
|
||||||
* @param string $key the key to check
|
* @param string|int $key the key to check
|
||||||
* @param array|ArrayAccess $array the array with keys to check
|
* @param array|ArrayAccess $array the array with keys to check
|
||||||
* @param bool $caseSensitive whether the key comparison should be case-sensitive
|
* @param bool $caseSensitive whether the key comparison should be case-sensitive
|
||||||
* @return bool whether the array contains the specified key
|
* @return bool whether the array contains the specified key
|
||||||
*/
|
*/
|
||||||
public static function keyExists($key, $array, $caseSensitive = true)
|
public static function keyExists($key, $array, $caseSensitive = true)
|
||||||
{
|
{
|
||||||
|
// ToDo: This check can be removed when the minimum PHP version is >= 8.1 (Yii2.2)
|
||||||
|
if (is_float($key)) {
|
||||||
|
$key = (int)$key;
|
||||||
|
}
|
||||||
|
|
||||||
if ($caseSensitive) {
|
if ($caseSensitive) {
|
||||||
// Function `isset` checks key faster but skips `null`, `array_key_exists` handles this case
|
if (is_array($array) && array_key_exists($key, $array)) {
|
||||||
// https://www.php.net/manual/en/function.array-key-exists.php#107786
|
|
||||||
if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Cannot use `array_has_key` on Objects for PHP 7.4+, therefore we need to check using [[ArrayAccess::offsetExists()]]
|
// Cannot use `array_has_key` on Objects for PHP 7.4+, therefore we need to check using [[ArrayAccess::offsetExists()]]
|
||||||
|
|||||||
@ -57,8 +57,8 @@ class Cookie extends \yii\base\BaseObject
|
|||||||
*/
|
*/
|
||||||
public $domain = '';
|
public $domain = '';
|
||||||
/**
|
/**
|
||||||
* @var int the timestamp at which the cookie expires. This is the server timestamp.
|
* @var int|string|\DateTimeInterface|null the timestamp or date at which the cookie expires. This is the server timestamp.
|
||||||
* Defaults to 0, meaning "until the browser is closed".
|
* Defaults to 0, meaning "until the browser is closed" (the same applies to `null`).
|
||||||
*/
|
*/
|
||||||
public $expire = 0;
|
public $expire = 0;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -51,7 +51,7 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA
|
|||||||
* Returns an iterator for traversing the cookies in the collection.
|
* Returns an iterator for traversing the cookies in the collection.
|
||||||
* This method is required by the SPL interface [[\IteratorAggregate]].
|
* This method is required by the SPL interface [[\IteratorAggregate]].
|
||||||
* It will be implicitly called when you use `foreach` to traverse the collection.
|
* It will be implicitly called when you use `foreach` to traverse the collection.
|
||||||
* @return ArrayIterator an iterator for traversing the cookies in the collection.
|
* @return ArrayIterator<string, Cookie> an iterator for traversing the cookies in the collection.
|
||||||
*/
|
*/
|
||||||
#[\ReturnTypeWillChange]
|
#[\ReturnTypeWillChange]
|
||||||
public function getIterator()
|
public function getIterator()
|
||||||
@ -113,7 +113,18 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA
|
|||||||
public function has($name)
|
public function has($name)
|
||||||
{
|
{
|
||||||
return isset($this->_cookies[$name]) && $this->_cookies[$name]->value !== ''
|
return isset($this->_cookies[$name]) && $this->_cookies[$name]->value !== ''
|
||||||
&& ($this->_cookies[$name]->expire === null || $this->_cookies[$name]->expire === 0 || $this->_cookies[$name]->expire >= time());
|
&& ($this->_cookies[$name]->expire === null
|
||||||
|
|| $this->_cookies[$name]->expire === 0
|
||||||
|
|| (
|
||||||
|
(is_string($this->_cookies[$name]->expire) && strtotime($this->_cookies[$name]->expire) >= time())
|
||||||
|
|| (
|
||||||
|
interface_exists('\\DateTimeInterface')
|
||||||
|
&& $this->_cookies[$name]->expire instanceof \DateTimeInterface
|
||||||
|
&& $this->_cookies[$name]->expire->getTimestamp() >= time()
|
||||||
|
)
|
||||||
|
|| $this->_cookies[$name]->expire >= time()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -174,7 +185,7 @@ class CookieCollection extends BaseObject implements \IteratorAggregate, \ArrayA
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the collection as a PHP array.
|
* Returns the collection as a PHP array.
|
||||||
* @return array the array representation of the collection.
|
* @return Cookie[] the array representation of the collection.
|
||||||
* The array keys are cookie names, and the array values are the corresponding cookie objects.
|
* The array keys are cookie names, and the array values are the corresponding cookie objects.
|
||||||
*/
|
*/
|
||||||
public function toArray()
|
public function toArray()
|
||||||
|
|||||||
@ -401,12 +401,21 @@ class Response extends \yii\base\Response
|
|||||||
}
|
}
|
||||||
foreach ($this->getCookies() as $cookie) {
|
foreach ($this->getCookies() as $cookie) {
|
||||||
$value = $cookie->value;
|
$value = $cookie->value;
|
||||||
if ($cookie->expire != 1 && isset($validationKey)) {
|
$expire = $cookie->expire;
|
||||||
|
if (is_string($expire)) {
|
||||||
|
$expire = strtotime($expire);
|
||||||
|
} elseif (interface_exists('\\DateTimeInterface') && $expire instanceof \DateTimeInterface) {
|
||||||
|
$expire = $expire->getTimestamp();
|
||||||
|
}
|
||||||
|
if ($expire === null || $expire === false) {
|
||||||
|
$expire = 0;
|
||||||
|
}
|
||||||
|
if ($expire != 1 && isset($validationKey)) {
|
||||||
$value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
|
$value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
|
||||||
}
|
}
|
||||||
if (PHP_VERSION_ID >= 70300) {
|
if (PHP_VERSION_ID >= 70300) {
|
||||||
setcookie($cookie->name, $value, [
|
setcookie($cookie->name, $value, [
|
||||||
'expires' => $cookie->expire,
|
'expires' => $expire,
|
||||||
'path' => $cookie->path,
|
'path' => $cookie->path,
|
||||||
'domain' => $cookie->domain,
|
'domain' => $cookie->domain,
|
||||||
'secure' => $cookie->secure,
|
'secure' => $cookie->secure,
|
||||||
@ -420,7 +429,7 @@ class Response extends \yii\base\Response
|
|||||||
if (!is_null($cookie->sameSite)) {
|
if (!is_null($cookie->sameSite)) {
|
||||||
$cookiePath .= '; samesite=' . $cookie->sameSite;
|
$cookiePath .= '; samesite=' . $cookie->sameSite;
|
||||||
}
|
}
|
||||||
setcookie($cookie->name, $value, $cookie->expire, $cookiePath, $cookie->domain, $cookie->secure, $cookie->httpOnly);
|
setcookie($cookie->name, $value, $expire, $cookiePath, $cookie->domain, $cookie->secure, $cookie->httpOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
tests/framework/db/BaseActiveRecordTest.php
Normal file
42
tests/framework/db/BaseActiveRecordTest.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace yiiunit\framework\db;
|
||||||
|
|
||||||
|
use yiiunit\data\ar\ActiveRecord;
|
||||||
|
|
||||||
|
abstract class BaseActiveRecordTest extends DatabaseTestCase
|
||||||
|
{
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
ActiveRecord::$db = $this->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideArrayValueWithChange()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'not an associative array with data change' => [
|
||||||
|
[1, 2, 3],
|
||||||
|
[1, 3, 2],
|
||||||
|
],
|
||||||
|
|
||||||
|
'associative array with data change case 1' => [
|
||||||
|
['pineapple' => 2, 'apple' => 5, 'banana' => 1],
|
||||||
|
['apple' => 5, 'pineapple' => 1, 'banana' => 3],
|
||||||
|
],
|
||||||
|
'associative array with data change case 2' => [
|
||||||
|
['pineapple' => 2, 'apple' => 5, 'banana' => 1],
|
||||||
|
['pineapple' => 2, 'apple' => 3, 'banana' => 1],
|
||||||
|
],
|
||||||
|
|
||||||
|
'filling an empty array' => [
|
||||||
|
[],
|
||||||
|
['pineapple' => 3, 'apple' => 1, 'banana' => 1],
|
||||||
|
],
|
||||||
|
'zeroing the array' => [
|
||||||
|
['pineapple' => 3, 'apple' => 1, 'banana' => 17],
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
37
tests/framework/db/mysql/BaseActiveRecordTest.php
Normal file
37
tests/framework/db/mysql/BaseActiveRecordTest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace yiiunit\framework\db\mysql;
|
||||||
|
|
||||||
|
use yiiunit\data\ar\Storage;
|
||||||
|
|
||||||
|
class BaseActiveRecordTest extends \yiiunit\framework\db\BaseActiveRecordTest
|
||||||
|
{
|
||||||
|
public $driverName = 'mysql';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/yiisoft/yii2/issues/19872
|
||||||
|
*
|
||||||
|
* @dataProvider provideArrayValueWithChange
|
||||||
|
*/
|
||||||
|
public function testJsonDirtyAttributesWithDataChange($actual, $modified)
|
||||||
|
{
|
||||||
|
if (version_compare($this->getConnection()->getSchema()->getServerVersion(), '5.7', '<')) {
|
||||||
|
$this->markTestSkipped('JSON columns are not supported in MySQL < 5.7');
|
||||||
|
}
|
||||||
|
if (version_compare(PHP_VERSION, '5.6', '<')) {
|
||||||
|
$this->markTestSkipped('JSON columns are not supported in PDO for PHP < 5.6');
|
||||||
|
}
|
||||||
|
|
||||||
|
$createdStorage = new Storage(['data' => $actual]);
|
||||||
|
|
||||||
|
$createdStorage->save();
|
||||||
|
|
||||||
|
$foundStorage = Storage::find()->limit(1)->one();
|
||||||
|
|
||||||
|
$this->assertNotNull($foundStorage);
|
||||||
|
|
||||||
|
$foundStorage->data = $modified;
|
||||||
|
|
||||||
|
$this->assertSame(['data' => $modified], $foundStorage->getDirtyAttributes());
|
||||||
|
}
|
||||||
|
}
|
||||||
46
tests/framework/db/pgsql/BaseActiveRecordTest.php
Normal file
46
tests/framework/db/pgsql/BaseActiveRecordTest.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace yiiunit\framework\db\pgsql;
|
||||||
|
|
||||||
|
use yii\db\JsonExpression;
|
||||||
|
use yiiunit\data\ar\ActiveRecord;
|
||||||
|
|
||||||
|
class BaseActiveRecordTest extends \yiiunit\framework\db\BaseActiveRecordTest
|
||||||
|
{
|
||||||
|
public $driverName = 'pgsql';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://github.com/yiisoft/yii2/issues/19872
|
||||||
|
*
|
||||||
|
* @dataProvider provideArrayValueWithChange
|
||||||
|
*/
|
||||||
|
public function testJsonDirtyAttributesWithDataChange($actual, $modified)
|
||||||
|
{
|
||||||
|
$createdStorage = new ArrayAndJsonType([
|
||||||
|
'json_col' => new JsonExpression($actual),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$createdStorage->save();
|
||||||
|
|
||||||
|
$foundStorage = ArrayAndJsonType::find()->limit(1)->one();
|
||||||
|
|
||||||
|
$this->assertNotNull($foundStorage);
|
||||||
|
|
||||||
|
$foundStorage->json_col = $modified;
|
||||||
|
|
||||||
|
$this->assertSame(['json_col' => $modified], $foundStorage->getDirtyAttributes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @property array id
|
||||||
|
* @property array json_col
|
||||||
|
*/
|
||||||
|
class ArrayAndJsonType extends ActiveRecord
|
||||||
|
{
|
||||||
|
public static function tableName()
|
||||||
|
{
|
||||||
|
return '{{%array_and_json_types}}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -135,6 +135,29 @@ class ArrayHelperTest extends TestCase
|
|||||||
$this->assertEquals('defaultValue', $default);
|
$this->assertEquals('defaultValue', $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testRemoveWithFloat()
|
||||||
|
{
|
||||||
|
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
|
||||||
|
$this->markTestSkipped('Using floats as array key is deprecated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$array = ['name' => 'b', 'age' => 3, 1.1 => null];
|
||||||
|
|
||||||
|
$name = ArrayHelper::remove($array, 'name');
|
||||||
|
$this->assertEquals($name, 'b');
|
||||||
|
$this->assertEquals($array, ['age' => 3, 1.1 => null]);
|
||||||
|
|
||||||
|
$floatVal = ArrayHelper::remove($array, 1.1);
|
||||||
|
$this->assertNull($floatVal);
|
||||||
|
$this->assertEquals($array, ['age' => 3]);
|
||||||
|
|
||||||
|
$default = ArrayHelper::remove($array, 'nonExisting', 'defaultValue');
|
||||||
|
$this->assertEquals('defaultValue', $default);
|
||||||
|
}
|
||||||
|
|
||||||
public function testRemoveValueMultiple()
|
public function testRemoveValueMultiple()
|
||||||
{
|
{
|
||||||
$array = [
|
$array = [
|
||||||
@ -506,14 +529,21 @@ class ArrayHelperTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @see https://github.com/yiisoft/yii2/pull/11549
|
* @see https://github.com/yiisoft/yii2/pull/11549
|
||||||
*/
|
*/
|
||||||
public function test()
|
public function testGetValueWithFloatKeys()
|
||||||
{
|
{
|
||||||
|
if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
|
||||||
|
$this->markTestSkipped('Using floats as array key is deprecated.');
|
||||||
|
}
|
||||||
|
|
||||||
$array = [];
|
$array = [];
|
||||||
$array[1.0] = 'some value';
|
$array[1.1] = 'some value';
|
||||||
|
$array[2.1] = null;
|
||||||
$result = ArrayHelper::getValue($array, 1.0);
|
|
||||||
|
|
||||||
|
$result = ArrayHelper::getValue($array, 1.2);
|
||||||
$this->assertEquals('some value', $result);
|
$this->assertEquals('some value', $result);
|
||||||
|
|
||||||
|
$result = ArrayHelper::getValue($array, 2.2);
|
||||||
|
$this->assertNull($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndex()
|
public function testIndex()
|
||||||
@ -712,6 +742,7 @@ class ArrayHelperTest extends TestCase
|
|||||||
'a' => 1,
|
'a' => 1,
|
||||||
'B' => 2,
|
'B' => 2,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->assertTrue(ArrayHelper::keyExists('a', $array));
|
$this->assertTrue(ArrayHelper::keyExists('a', $array));
|
||||||
$this->assertFalse(ArrayHelper::keyExists('b', $array));
|
$this->assertFalse(ArrayHelper::keyExists('b', $array));
|
||||||
$this->assertTrue(ArrayHelper::keyExists('B', $array));
|
$this->assertTrue(ArrayHelper::keyExists('B', $array));
|
||||||
@ -723,6 +754,27 @@ class ArrayHelperTest extends TestCase
|
|||||||
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
|
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
3.3 => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists(1, $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists(1.1, $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists(2, $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists('2', $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists(2.2, $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists(3, $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists(3.3, $array));
|
||||||
|
}
|
||||||
|
|
||||||
public function testKeyExistsArrayAccess()
|
public function testKeyExistsArrayAccess()
|
||||||
{
|
{
|
||||||
$array = new TraversableArrayAccessibleObject([
|
$array = new TraversableArrayAccessibleObject([
|
||||||
|
|||||||
@ -380,21 +380,116 @@ class ResponseTest extends \yiiunit\TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSameSiteCookie()
|
/**
|
||||||
|
* @dataProvider cookiesTestProvider
|
||||||
|
*/
|
||||||
|
public function testCookies($cookieConfig, $expected)
|
||||||
{
|
{
|
||||||
$response = new Response();
|
$response = new Response();
|
||||||
$response->cookies->add(new Cookie([
|
$response->cookies->add(new Cookie(array_merge(
|
||||||
'name' => 'test',
|
[
|
||||||
'value' => 'testValue',
|
'name' => 'test',
|
||||||
'sameSite' => Cookie::SAME_SITE_STRICT,
|
'value' => 'testValue',
|
||||||
]));
|
],
|
||||||
|
$cookieConfig
|
||||||
|
)));
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
$response->send();
|
$response->send();
|
||||||
$content = ob_get_clean();
|
$content = ob_get_clean();
|
||||||
|
|
||||||
// Only way to test is that it doesn't create any errors
|
$cookies = $this->parseHeaderCookies();
|
||||||
$this->assertEquals('', $content);
|
if ($cookies === false) {
|
||||||
|
// Unable to resolve cookies, only way to test is that it doesn't create any errors
|
||||||
|
$this->assertEquals('', $content);
|
||||||
|
} else {
|
||||||
|
$testCookie = $cookies['test'];
|
||||||
|
$actual = array_intersect_key($testCookie, $expected);
|
||||||
|
ksort($actual);
|
||||||
|
ksort($expected);
|
||||||
|
$this->assertEquals($expected, $actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cookiesTestProvider()
|
||||||
|
{
|
||||||
|
$expireInt = time() + 3600;
|
||||||
|
$expireString = date('D, d-M-Y H:i:s', $expireInt) . ' GMT';
|
||||||
|
|
||||||
|
$testCases = [
|
||||||
|
'same-site' => [
|
||||||
|
['sameSite' => Cookie::SAME_SITE_STRICT],
|
||||||
|
['samesite' => Cookie::SAME_SITE_STRICT],
|
||||||
|
],
|
||||||
|
'expire-as-int' => [
|
||||||
|
['expire' => $expireInt],
|
||||||
|
['expires' => $expireString],
|
||||||
|
],
|
||||||
|
'expire-as-string' => [
|
||||||
|
['expire' => $expireString],
|
||||||
|
['expires' => $expireString],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
|
||||||
|
$testCases = array_merge($testCases, [
|
||||||
|
'expire-as-date_time' => [
|
||||||
|
['expire' => new \DateTime('@' . $expireInt)],
|
||||||
|
['expires' => $expireString],
|
||||||
|
],
|
||||||
|
'expire-as-date_time_immutable' => [
|
||||||
|
['expire' => new \DateTimeImmutable('@' . $expireInt)],
|
||||||
|
['expires' => $expireString],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $testCases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to parse cookies set in the response headers.
|
||||||
|
* When running PHP on the CLI headers are not available (the `headers_list()` function always returns an
|
||||||
|
* empty array). If possible use xDebug: http://xdebug.org/docs/all_functions#xdebug_get_headers
|
||||||
|
* @param $name
|
||||||
|
* @return array|false
|
||||||
|
*/
|
||||||
|
protected function parseHeaderCookies() {
|
||||||
|
|
||||||
|
if (!function_exists('xdebug_get_headers')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cookies = [];
|
||||||
|
foreach(xdebug_get_headers() as $header) {
|
||||||
|
if (strpos($header, 'Set-Cookie: ') !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = null;
|
||||||
|
$params = [];
|
||||||
|
$pairs = explode(';', substr($header, 12));
|
||||||
|
foreach ($pairs as $index => $pair) {
|
||||||
|
$pair = trim($pair);
|
||||||
|
if (strpos($pair, '=') === false) {
|
||||||
|
$params[strtolower($pair)] = true;
|
||||||
|
} else {
|
||||||
|
list($paramName, $paramValue) = explode('=', $pair, 2);
|
||||||
|
if ($index === 0) {
|
||||||
|
$name = $paramName;
|
||||||
|
$params['value'] = urldecode($paramValue);
|
||||||
|
} else {
|
||||||
|
$params[strtolower($paramName)] = urldecode($paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($name === null) {
|
||||||
|
throw new \Exception('Could not determine cookie name for header "' . $header . '".');
|
||||||
|
}
|
||||||
|
$cookies[$name] = $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user