mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-16 07:11:19 +08:00
Fix #18026: Fix ArrayHelper::getValue()
did not work with ArrayAccess
objects
This commit is contained in:
@ -6,6 +6,7 @@ Yii Framework 2 Change Log
|
|||||||
|
|
||||||
- Bug #18028: Fix division by zero exception in Table.php::calculateRowHeight (fourhundredfour)
|
- Bug #18028: Fix division by zero exception in Table.php::calculateRowHeight (fourhundredfour)
|
||||||
- Enh #18019: Allow jQuery 3.5.0 to be installed (wouter90)
|
- Enh #18019: Allow jQuery 3.5.0 to be installed (wouter90)
|
||||||
|
- Bug #18026: Fix `ArrayHelper::getValue()` did not work with `ArrayAccess` objects (mikk150)
|
||||||
|
|
||||||
|
|
||||||
2.0.35 May 02, 2020
|
2.0.35 May 02, 2020
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
namespace yii\helpers;
|
namespace yii\helpers;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Traversable;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yii\base\Arrayable;
|
use yii\base\Arrayable;
|
||||||
use yii\base\InvalidArgumentException;
|
use yii\base\InvalidArgumentException;
|
||||||
@ -194,7 +196,7 @@ class BaseArrayHelper
|
|||||||
$key = $lastKey;
|
$key = $lastKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
|
if (static::keyExists($key, $array)) {
|
||||||
return $array[$key];
|
return $array[$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,12 +205,13 @@ class BaseArrayHelper
|
|||||||
$key = substr($key, $pos + 1);
|
$key = substr($key, $pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (static::isArrayAccess($array)) {
|
||||||
|
return static::keyExists($key, $array) ? $array[$key] : $default;
|
||||||
|
}
|
||||||
if (is_object($array)) {
|
if (is_object($array)) {
|
||||||
// this is expected to fail if the property does not exist, or __get() is not implemented
|
// this is expected to fail if the property does not exist, or __get() is not implemented
|
||||||
// it is not reliably possible to check whether a property is accessible beforehand
|
// it is not reliably possible to check whether a property is accessible beforehand
|
||||||
return $array->$key;
|
return $array->$key;
|
||||||
} elseif (is_array($array)) {
|
|
||||||
return (isset($array[$key]) || array_key_exists($key, $array)) ? $array[$key] : $default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $default;
|
return $default;
|
||||||
@ -593,7 +596,7 @@ class BaseArrayHelper
|
|||||||
* 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 $key the key to check
|
||||||
* @param array $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
|
||||||
*/
|
*/
|
||||||
@ -602,7 +605,15 @@ class BaseArrayHelper
|
|||||||
if ($caseSensitive) {
|
if ($caseSensitive) {
|
||||||
// Function `isset` checks key faster but skips `null`, `array_key_exists` handles this case
|
// Function `isset` checks key faster but skips `null`, `array_key_exists` handles this case
|
||||||
// https://secure.php.net/manual/en/function.array-key-exists.php#107786
|
// https://secure.php.net/manual/en/function.array-key-exists.php#107786
|
||||||
return isset($array[$key]) || array_key_exists($key, $array);
|
if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Cannot use `array_has_key` on Objects for PHP 7.4+, therefore we need to check using [[ArrayAccess::offsetExists()]]
|
||||||
|
return $array instanceof ArrayAccess && $array->offsetExists($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($array instanceof ArrayAccess) {
|
||||||
|
throw new InvalidArgumentException('Second parameter($array) cannot be ArrayAccess in case insensitive mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array_keys($array) as $k) {
|
foreach (array_keys($array) as $k) {
|
||||||
@ -805,12 +816,12 @@ class BaseArrayHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether an array or [[\Traversable]] contains an element.
|
* Check whether an array or [[Traversable]] contains an element.
|
||||||
*
|
*
|
||||||
* This method does the same as the PHP function [in_array()](https://secure.php.net/manual/en/function.in-array.php)
|
* This method does the same as the PHP function [in_array()](https://secure.php.net/manual/en/function.in-array.php)
|
||||||
* but additionally works for objects that implement the [[\Traversable]] interface.
|
* but additionally works for objects that implement the [[Traversable]] interface.
|
||||||
* @param mixed $needle The value to look for.
|
* @param mixed $needle The value to look for.
|
||||||
* @param array|\Traversable $haystack The set of values to search.
|
* @param array|Traversable $haystack The set of values to search.
|
||||||
* @param bool $strict Whether to enable strict (`===`) comparison.
|
* @param bool $strict Whether to enable strict (`===`) comparison.
|
||||||
* @return bool `true` if `$needle` was found in `$haystack`, `false` otherwise.
|
* @return bool `true` if `$needle` was found in `$haystack`, `false` otherwise.
|
||||||
* @throws InvalidArgumentException if `$haystack` is neither traversable nor an array.
|
* @throws InvalidArgumentException if `$haystack` is neither traversable nor an array.
|
||||||
@ -819,7 +830,7 @@ class BaseArrayHelper
|
|||||||
*/
|
*/
|
||||||
public static function isIn($needle, $haystack, $strict = false)
|
public static function isIn($needle, $haystack, $strict = false)
|
||||||
{
|
{
|
||||||
if ($haystack instanceof \Traversable) {
|
if ($haystack instanceof Traversable) {
|
||||||
foreach ($haystack as $value) {
|
foreach ($haystack as $value) {
|
||||||
if ($needle == $value && (!$strict || $needle === $value)) {
|
if ($needle == $value && (!$strict || $needle === $value)) {
|
||||||
return true;
|
return true;
|
||||||
@ -835,27 +846,43 @@ class BaseArrayHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a variable is an array or [[\Traversable]].
|
* Checks whether a variable is an array or [[Traversable]].
|
||||||
*
|
*
|
||||||
* This method does the same as the PHP function [is_array()](https://secure.php.net/manual/en/function.is-array.php)
|
* This method does the same as the PHP function [is_array()](https://secure.php.net/manual/en/function.is-array.php)
|
||||||
* but additionally works on objects that implement the [[\Traversable]] interface.
|
* but additionally works on objects that implement the [[Traversable]] interface.
|
||||||
* @param mixed $var The variable being evaluated.
|
* @param mixed $var The variable being evaluated.
|
||||||
* @return bool whether $var is array-like
|
* @return bool whether $var can be traversed via foreach
|
||||||
* @see https://secure.php.net/manual/en/function.is-array.php
|
* @see https://secure.php.net/manual/en/function.is-array.php
|
||||||
* @since 2.0.8
|
* @since 2.0.8
|
||||||
*/
|
*/
|
||||||
public static function isTraversable($var)
|
public static function isTraversable($var)
|
||||||
{
|
{
|
||||||
return is_array($var) || $var instanceof \Traversable;
|
return is_array($var) || $var instanceof Traversable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether an array or [[\Traversable]] is a subset of another array or [[\Traversable]].
|
* Checks whether a variable is an array or [[\ArrayAccess]].
|
||||||
|
*
|
||||||
|
* This method does the same as the PHP function [is_array()](https://secure.php.net/manual/en/function.is-array.php)
|
||||||
|
* but additionally works on objects that implement the [[\ArrayAccess]] interface.
|
||||||
|
* @param mixed $var The variable being evaluated.
|
||||||
|
* @return bool whether data on $var can be accessed as arrays
|
||||||
|
* @see https://secure.php.net/manual/en/function.is-array.php
|
||||||
|
* @since 2.0.36
|
||||||
|
*/
|
||||||
|
public static function isArrayAccess($var)
|
||||||
|
{
|
||||||
|
return is_array($var) || $var instanceof \ArrayAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an array or [[Traversable]] is a subset of another array or [[Traversable]].
|
||||||
*
|
*
|
||||||
* This method will return `true`, if all elements of `$needles` are contained in
|
* This method will return `true`, if all elements of `$needles` are contained in
|
||||||
* `$haystack`. If at least one element is missing, `false` will be returned.
|
* `$haystack`. If at least one element is missing, `false` will be returned.
|
||||||
* @param array|\Traversable $needles The values that must **all** be in `$haystack`.
|
* @param array|Traversable $needles The values that must **all** be in `$haystack`.
|
||||||
* @param array|\Traversable $haystack The set of value to search.
|
* @param array|Traversable $haystack The set of value to search.
|
||||||
* @param bool $strict Whether to enable strict (`===`) comparison.
|
* @param bool $strict Whether to enable strict (`===`) comparison.
|
||||||
* @throws InvalidArgumentException if `$haystack` or `$needles` is neither traversable nor an array.
|
* @throws InvalidArgumentException if `$haystack` or `$needles` is neither traversable nor an array.
|
||||||
* @return bool `true` if `$needles` is a subset of `$haystack`, `false` otherwise.
|
* @return bool `true` if `$needles` is a subset of `$haystack`, `false` otherwise.
|
||||||
@ -863,7 +890,7 @@ class BaseArrayHelper
|
|||||||
*/
|
*/
|
||||||
public static function isSubset($needles, $haystack, $strict = false)
|
public static function isSubset($needles, $haystack, $strict = false)
|
||||||
{
|
{
|
||||||
if (is_array($needles) || $needles instanceof \Traversable) {
|
if (is_array($needles) || $needles instanceof Traversable) {
|
||||||
foreach ($needles as $needle) {
|
foreach ($needles as $needle) {
|
||||||
if (!static::isIn($needle, $haystack, $strict)) {
|
if (!static::isIn($needle, $haystack, $strict)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
namespace yiiunit\framework\helpers;
|
namespace yiiunit\framework\helpers;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Iterator;
|
||||||
use yii\base\BaseObject;
|
use yii\base\BaseObject;
|
||||||
use yii\data\Sort;
|
use yii\data\Sort;
|
||||||
use yii\helpers\ArrayHelper;
|
use yii\helpers\ArrayHelper;
|
||||||
@ -41,6 +43,84 @@ class Post3 extends BaseObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ArrayAccessibleObject implements ArrayAccess
|
||||||
|
{
|
||||||
|
protected $container = [];
|
||||||
|
|
||||||
|
public function __construct($container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
|
if (is_null($offset)) {
|
||||||
|
$this->container[] = $value;
|
||||||
|
} else {
|
||||||
|
$this->container[$offset] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
|
return array_key_exists($offset, $this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
|
unset($this->container[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
|
return $this->offsetExists($offset) ? $this->container[$offset] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TraversableArrayAccessibleObject extends ArrayAccessibleObject implements Iterator
|
||||||
|
{
|
||||||
|
private $position = 0;
|
||||||
|
|
||||||
|
public function __construct($container)
|
||||||
|
{
|
||||||
|
$this->position = 0;
|
||||||
|
|
||||||
|
parent::__construct($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getContainerKey($keyIndex)
|
||||||
|
{
|
||||||
|
$keys = array_keys($this->container);
|
||||||
|
return array_key_exists($keyIndex, $keys) ? $keys[$keyIndex] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rewind()
|
||||||
|
{
|
||||||
|
$this->position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
return $this->offsetGet($this->getContainerKey($this->position));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key()
|
||||||
|
{
|
||||||
|
return $this->getContainerKey($this->position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
++$this->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function valid()
|
||||||
|
{
|
||||||
|
$key = $this->getContainerKey($this->position);
|
||||||
|
return !(!$key || !$this->offsetExists($key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group helpers
|
* @group helpers
|
||||||
*/
|
*/
|
||||||
@ -734,6 +814,33 @@ class ArrayHelperTest extends TestCase
|
|||||||
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
|
$this->assertFalse(ArrayHelper::keyExists('c', $array, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testKeyExistsArrayAccess()
|
||||||
|
{
|
||||||
|
$array = new TraversableArrayAccessibleObject([
|
||||||
|
'a' => 1,
|
||||||
|
'B' => 2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists('a', $array));
|
||||||
|
$this->assertFalse(ArrayHelper::keyExists('b', $array));
|
||||||
|
$this->assertTrue(ArrayHelper::keyExists('B', $array));
|
||||||
|
$this->assertFalse(ArrayHelper::keyExists('c', $array));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \yii\base\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Second parameter($array) cannot be ArrayAccess in case insensitive mode
|
||||||
|
*/
|
||||||
|
public function testKeyExistsArrayAccessCaseInsensitiveThrowsError()
|
||||||
|
{
|
||||||
|
$array = new TraversableArrayAccessibleObject([
|
||||||
|
'a' => 1,
|
||||||
|
'B' => 2,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ArrayHelper::keyExists('a', $array, false);
|
||||||
|
}
|
||||||
|
|
||||||
public function valueProvider()
|
public function valueProvider()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -830,6 +937,45 @@ class ArrayHelperTest extends TestCase
|
|||||||
$this->assertEquals(23, ArrayHelper::getValue($arrayObject, 'nonExisting'));
|
$this->assertEquals(23, ArrayHelper::getValue($arrayObject, 'nonExisting'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetValueFromArrayAccess()
|
||||||
|
{
|
||||||
|
$arrayAccessibleObject = new ArrayAccessibleObject([
|
||||||
|
'one' => 1,
|
||||||
|
'two' => 2,
|
||||||
|
'three' => 3,
|
||||||
|
'key.with.dot' => 'dot',
|
||||||
|
'null' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(1, ArrayHelper::getValue($arrayAccessibleObject, 'one'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetValueWithDotsFromArrayAccess()
|
||||||
|
{
|
||||||
|
$arrayAccessibleObject = new ArrayAccessibleObject([
|
||||||
|
'one' => 1,
|
||||||
|
'two' => 2,
|
||||||
|
'three' => 3,
|
||||||
|
'key.with.dot' => 'dot',
|
||||||
|
'null' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals('dot', ArrayHelper::getValue($arrayAccessibleObject, 'key.with.dot'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetValueNonexistingArrayAccess()
|
||||||
|
{
|
||||||
|
$arrayAccessibleObject = new ArrayAccessibleObject([
|
||||||
|
'one' => 1,
|
||||||
|
'two' => 2,
|
||||||
|
'three' => 3,
|
||||||
|
'key.with.dot' => 'dot',
|
||||||
|
'null' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(null, ArrayHelper::getValue($arrayAccessibleObject, 'four'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data provider for [[testSetValue()]].
|
* Data provider for [[testSetValue()]].
|
||||||
* @return array test data
|
* @return array test data
|
||||||
|
Reference in New Issue
Block a user