mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-10-31 10:39:59 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1698 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1698 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| /**
 | ||
|  * @link https://www.yiiframework.com/
 | ||
|  * @copyright Copyright (c) 2008 Yii Software LLC
 | ||
|  * @license https://www.yiiframework.com/license/
 | ||
|  */
 | ||
| 
 | ||
| namespace yiiunit\framework\helpers;
 | ||
| 
 | ||
| use ArrayAccess;
 | ||
| use Iterator;
 | ||
| use yii\base\BaseObject;
 | ||
| use yii\base\Model;
 | ||
| use yii\data\Sort;
 | ||
| use yii\helpers\ArrayHelper;
 | ||
| use yiiunit\TestCase;
 | ||
| 
 | ||
| /**
 | ||
|  * @group helpers
 | ||
|  */
 | ||
| class ArrayHelperTest extends TestCase
 | ||
| {
 | ||
|     protected function setUp()
 | ||
|     {
 | ||
|         parent::setUp();
 | ||
| 
 | ||
|         // destroy application, Helper must work without Yii::$app
 | ||
|         $this->destroyApplication();
 | ||
|     }
 | ||
| 
 | ||
|     public function testToArray()
 | ||
|     {
 | ||
|         $dataArrayable = $this->getMockBuilder('yii\\base\\Arrayable')->getMock();
 | ||
|         $dataArrayable->method('toArray')->willReturn([]);
 | ||
|         $this->assertEquals([], ArrayHelper::toArray($dataArrayable));
 | ||
|         $this->assertEquals(['foo'], ArrayHelper::toArray('foo'));
 | ||
|         $object = new Post1();
 | ||
|         $this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object));
 | ||
|         $object = new Post2();
 | ||
|         $this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object));
 | ||
| 
 | ||
|         $object1 = new Post1();
 | ||
|         $object2 = new Post2();
 | ||
|         $this->assertEquals([
 | ||
|             get_object_vars($object1),
 | ||
|             get_object_vars($object2),
 | ||
|         ], ArrayHelper::toArray([
 | ||
|             $object1,
 | ||
|             $object2,
 | ||
|         ]));
 | ||
| 
 | ||
|         $object = new Post2();
 | ||
|         $this->assertEquals([
 | ||
|             'id' => 123,
 | ||
|             'secret' => 's',
 | ||
|             '_content' => 'test',
 | ||
|             'length' => 4,
 | ||
|         ], ArrayHelper::toArray($object, [
 | ||
|             $object::className() => [
 | ||
|                 'id', 'secret',
 | ||
|                 '_content' => 'content',
 | ||
|                 'length' => function ($post) {
 | ||
|                     return strlen($post->content);
 | ||
|                 },
 | ||
|             ],
 | ||
|         ]));
 | ||
| 
 | ||
|         $object = new Post3();
 | ||
|         $this->assertEquals(get_object_vars($object), ArrayHelper::toArray($object, [], false));
 | ||
|         $this->assertEquals([
 | ||
|             'id' => 33,
 | ||
|             'subObject' => [
 | ||
|                 'id' => 123,
 | ||
|                 'content' => 'test',
 | ||
|             ],
 | ||
|         ], ArrayHelper::toArray($object));
 | ||
| 
 | ||
|         //recursive with attributes of object and sub-object
 | ||
|         $subObject = $object->subObject;
 | ||
|         $this->assertEquals([
 | ||
|             'id' => 33,
 | ||
|             'id_plus_1' => 34,
 | ||
|             'subObject' => [
 | ||
|                 'id' => 123,
 | ||
|                 'id_plus_1' => 124,
 | ||
|             ],
 | ||
|         ], ArrayHelper::toArray($object, [
 | ||
|             $object::className() => [
 | ||
|                 'id', 'subObject',
 | ||
|                 'id_plus_1' => function ($post) {
 | ||
|                     return $post->id + 1;
 | ||
|                 },
 | ||
|             ],
 | ||
|             $subObject::className() => [
 | ||
|                 'id',
 | ||
|                 'id_plus_1' => function ($post) {
 | ||
|                     return $post->id + 1;
 | ||
|                 },
 | ||
|             ],
 | ||
|         ]));
 | ||
| 
 | ||
|         //recursive with attributes of subobject only
 | ||
|         $this->assertEquals([
 | ||
|             'id' => 33,
 | ||
|             'subObject' => [
 | ||
|                 'id' => 123,
 | ||
|                 'id_plus_1' => 124,
 | ||
|             ],
 | ||
|         ], ArrayHelper::toArray($object, [
 | ||
|             $subObject::className() => [
 | ||
|                 'id',
 | ||
|                 'id_plus_1' => function ($post) {
 | ||
|                     return $post->id + 1;
 | ||
|                 },
 | ||
|             ],
 | ||
|         ]));
 | ||
| 
 | ||
|         // DateTime test
 | ||
|         $this->assertEquals([
 | ||
|             'date' => '2021-09-13 15:16:17.000000',
 | ||
|             'timezone_type' => 3,
 | ||
|             'timezone' => 'UTC',
 | ||
|         ], ArrayHelper::toArray(new \DateTime('2021-09-13 15:16:17', new \DateTimeZone('UTC'))));
 | ||
|     }
 | ||
| 
 | ||
|     public function testRemove()
 | ||
|     {
 | ||
|         $array = ['name' => 'b', 'age' => 3];
 | ||
|         $name = ArrayHelper::remove($array, 'name');
 | ||
| 
 | ||
|         $this->assertEquals($name, 'b');
 | ||
|         $this->assertEquals($array, ['age' => 3]);
 | ||
| 
 | ||
|         $default = ArrayHelper::remove($array, 'nonExisting', 'defaultValue');
 | ||
|         $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()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'Bob' => 'Dylan',
 | ||
|             'Michael' => 'Jackson',
 | ||
|             'Mick' => 'Jagger',
 | ||
|             'Janet' => 'Jackson',
 | ||
|         ];
 | ||
| 
 | ||
|         $removed = ArrayHelper::removeValue($array, 'Jackson');
 | ||
| 
 | ||
|         $this->assertEquals([
 | ||
|             'Bob' => 'Dylan',
 | ||
|             'Mick' => 'Jagger',
 | ||
|         ], $array);
 | ||
|         $this->assertEquals([
 | ||
|             'Michael' => 'Jackson',
 | ||
|             'Janet' => 'Jackson',
 | ||
|         ], $removed);
 | ||
|     }
 | ||
| 
 | ||
|     public function testRemoveValueNotExisting()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'Bob' => 'Dylan',
 | ||
|             'Michael' => 'Jackson',
 | ||
|             'Mick' => 'Jagger',
 | ||
|             'Janet' => 'Jackson',
 | ||
|         ];
 | ||
| 
 | ||
|         $removed = ArrayHelper::removeValue($array, 'Marley');
 | ||
| 
 | ||
|         $this->assertEquals([
 | ||
|             'Bob' => 'Dylan',
 | ||
|             'Michael' => 'Jackson',
 | ||
|             'Mick' => 'Jagger',
 | ||
|             'Janet' => 'Jackson',
 | ||
|         ], $array);
 | ||
|         $this->assertEquals([], $removed);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMultisort()
 | ||
|     {
 | ||
|         // empty key
 | ||
|         $dataEmpty = [];
 | ||
|         ArrayHelper::multisort($dataEmpty, '');
 | ||
|         $this->assertEquals([], $dataEmpty);
 | ||
| 
 | ||
|         // single key
 | ||
|         $array = [
 | ||
|             ['name' => 'b', 'age' => 3],
 | ||
|             ['name' => 'a', 'age' => 1],
 | ||
|             ['name' => 'c', 'age' => 2],
 | ||
|         ];
 | ||
|         ArrayHelper::multisort($array, 'name');
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 1], $array[0]);
 | ||
|         $this->assertEquals(['name' => 'b', 'age' => 3], $array[1]);
 | ||
|         $this->assertEquals(['name' => 'c', 'age' => 2], $array[2]);
 | ||
| 
 | ||
|         // multiple keys
 | ||
|         $array = [
 | ||
|             ['name' => 'b', 'age' => 3],
 | ||
|             ['name' => 'a', 'age' => 2],
 | ||
|             ['name' => 'a', 'age' => 1],
 | ||
|         ];
 | ||
|         ArrayHelper::multisort($array, ['name', 'age']);
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 1], $array[0]);
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 2], $array[1]);
 | ||
|         $this->assertEquals(['name' => 'b', 'age' => 3], $array[2]);
 | ||
| 
 | ||
|         // case-insensitive
 | ||
|         $array = [
 | ||
|             ['name' => 'a', 'age' => 3],
 | ||
|             ['name' => 'b', 'age' => 2],
 | ||
|             ['name' => 'B', 'age' => 4],
 | ||
|             ['name' => 'A', 'age' => 1],
 | ||
|         ];
 | ||
| 
 | ||
|         ArrayHelper::multisort($array, ['name', 'age'], SORT_ASC, [SORT_STRING, SORT_REGULAR]);
 | ||
|         $this->assertEquals(['name' => 'A', 'age' => 1], $array[0]);
 | ||
|         $this->assertEquals(['name' => 'B', 'age' => 4], $array[1]);
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 3], $array[2]);
 | ||
|         $this->assertEquals(['name' => 'b', 'age' => 2], $array[3]);
 | ||
| 
 | ||
|         ArrayHelper::multisort($array, ['name', 'age'], SORT_ASC, [SORT_STRING | SORT_FLAG_CASE, SORT_REGULAR]);
 | ||
|         $this->assertEquals(['name' => 'A', 'age' => 1], $array[0]);
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 3], $array[1]);
 | ||
|         $this->assertEquals(['name' => 'b', 'age' => 2], $array[2]);
 | ||
|         $this->assertEquals(['name' => 'B', 'age' => 4], $array[3]);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMultisortNestedObjects()
 | ||
|     {
 | ||
|         $obj1 = new \stdClass();
 | ||
|         $obj1->type = 'def';
 | ||
|         $obj1->owner = $obj1;
 | ||
| 
 | ||
|         $obj2 = new \stdClass();
 | ||
|         $obj2->type = 'abc';
 | ||
|         $obj2->owner = $obj2;
 | ||
| 
 | ||
|         $obj3 = new \stdClass();
 | ||
|         $obj3->type = 'abc';
 | ||
|         $obj3->owner = $obj3;
 | ||
| 
 | ||
|         $models = [
 | ||
|             $obj1,
 | ||
|             $obj2,
 | ||
|             $obj3,
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($obj2, $obj3);
 | ||
| 
 | ||
|         ArrayHelper::multisort($models, 'type', SORT_ASC);
 | ||
|         $this->assertEquals($obj2, $models[0]);
 | ||
|         $this->assertEquals($obj3, $models[1]);
 | ||
|         $this->assertEquals($obj1, $models[2]);
 | ||
| 
 | ||
|         ArrayHelper::multisort($models, 'type', SORT_DESC);
 | ||
|         $this->assertEquals($obj1, $models[0]);
 | ||
|         $this->assertEquals($obj2, $models[1]);
 | ||
|         $this->assertEquals($obj3, $models[2]);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMultisortUseSort()
 | ||
|     {
 | ||
|         // single key
 | ||
|         $sort = new Sort([
 | ||
|             'attributes' => ['name', 'age'],
 | ||
|             'defaultOrder' => ['name' => SORT_ASC],
 | ||
|             'params' => [],
 | ||
|         ]);
 | ||
|         $orders = $sort->getOrders();
 | ||
| 
 | ||
|         $array = [
 | ||
|             ['name' => 'b', 'age' => 3],
 | ||
|             ['name' => 'a', 'age' => 1],
 | ||
|             ['name' => 'c', 'age' => 2],
 | ||
|         ];
 | ||
|         ArrayHelper::multisort($array, array_keys($orders), array_values($orders));
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 1], $array[0]);
 | ||
|         $this->assertEquals(['name' => 'b', 'age' => 3], $array[1]);
 | ||
|         $this->assertEquals(['name' => 'c', 'age' => 2], $array[2]);
 | ||
| 
 | ||
|         // multiple keys
 | ||
|         $sort = new Sort([
 | ||
|             'attributes' => ['name', 'age'],
 | ||
|             'defaultOrder' => ['name' => SORT_ASC, 'age' => SORT_DESC],
 | ||
|             'params' => [],
 | ||
|         ]);
 | ||
|         $orders = $sort->getOrders();
 | ||
| 
 | ||
|         $array = [
 | ||
|             ['name' => 'b', 'age' => 3],
 | ||
|             ['name' => 'a', 'age' => 2],
 | ||
|             ['name' => 'a', 'age' => 1],
 | ||
|         ];
 | ||
|         ArrayHelper::multisort($array, array_keys($orders), array_values($orders));
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 2], $array[0]);
 | ||
|         $this->assertEquals(['name' => 'a', 'age' => 1], $array[1]);
 | ||
|         $this->assertEquals(['name' => 'b', 'age' => 3], $array[2]);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMultisortClosure()
 | ||
|     {
 | ||
|         $changelog = [
 | ||
|             '- Enh #123: test1',
 | ||
|             '- Bug #125: test2',
 | ||
|             '- Bug #123: test2',
 | ||
|             '- Enh: test3',
 | ||
|             '- Bug: test4',
 | ||
|         ];
 | ||
|         $i = 0;
 | ||
|         ArrayHelper::multisort($changelog, function ($line) use (&$i) {
 | ||
|             if (preg_match('/^- (Enh|Bug)( #\d+)?: .+$/', $line, $m)) {
 | ||
|                 $o = ['Bug' => 'C', 'Enh' => 'D'];
 | ||
|                 return $o[$m[1]] . ' ' . (!empty($m[2]) ? $m[2] : 'AAAA' . $i++);
 | ||
|             }
 | ||
| 
 | ||
|             return 'B' . $i++;
 | ||
|         }, SORT_ASC, SORT_NATURAL);
 | ||
|         $this->assertEquals([
 | ||
|             '- Bug #123: test2',
 | ||
|             '- Bug #125: test2',
 | ||
|             '- Bug: test4',
 | ||
|             '- Enh #123: test1',
 | ||
|             '- Enh: test3',
 | ||
|         ], $changelog);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMultisortInvalidParamExceptionDirection()
 | ||
|     {
 | ||
|         $this->expectException('yii\base\InvalidParamException');
 | ||
|         $data = ['foo' => 'bar'];
 | ||
|         ArrayHelper::multisort($data, ['foo'], []);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMultisortInvalidParamExceptionSortFlag()
 | ||
|     {
 | ||
|         $this->expectException('yii\base\InvalidParamException');
 | ||
|         $data = ['foo' => 'bar'];
 | ||
|         ArrayHelper::multisort($data, ['foo'], ['foo'], []);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMerge()
 | ||
|     {
 | ||
|         $a = [
 | ||
|             'name' => 'Yii',
 | ||
|             'version' => '1.0',
 | ||
|             'options' => [
 | ||
|                 'namespace' => false,
 | ||
|                 'unittest' => false,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'mvc',
 | ||
|             ],
 | ||
|         ];
 | ||
|         $b = [
 | ||
|             'version' => '1.1',
 | ||
|             'options' => [
 | ||
|                 'unittest' => true,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'gii',
 | ||
|             ],
 | ||
|         ];
 | ||
|         $c = [
 | ||
|             'version' => '2.0',
 | ||
|             'options' => [
 | ||
|                 'namespace' => true,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'debug',
 | ||
|             ],
 | ||
|             'foo',
 | ||
|         ];
 | ||
| 
 | ||
|         $result = ArrayHelper::merge($a, $b, $c);
 | ||
|         $expected = [
 | ||
|             'name' => 'Yii',
 | ||
|             'version' => '2.0',
 | ||
|             'options' => [
 | ||
|                 'namespace' => true,
 | ||
|                 'unittest' => true,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'mvc',
 | ||
|                 'gii',
 | ||
|                 'debug',
 | ||
|             ],
 | ||
|             'foo',
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMergeWithNumericKeys()
 | ||
|     {
 | ||
|         $a = [10 => [1]];
 | ||
|         $b = [10 => [2]];
 | ||
| 
 | ||
|         $result = ArrayHelper::merge($a, $b);
 | ||
| 
 | ||
|         $expected = [10 => [1], 11 => [2]];
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMergeWithUnset()
 | ||
|     {
 | ||
|         $a = [
 | ||
|             'name' => 'Yii',
 | ||
|             'version' => '1.0',
 | ||
|             'options' => [
 | ||
|                 'namespace' => false,
 | ||
|                 'unittest' => false,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'mvc',
 | ||
|             ],
 | ||
|         ];
 | ||
|         $b = [
 | ||
|             'version' => '1.1',
 | ||
|             'options' => new \yii\helpers\UnsetArrayValue(),
 | ||
|             'features' => [
 | ||
|                 'gii',
 | ||
|             ],
 | ||
|         ];
 | ||
| 
 | ||
|         $result = ArrayHelper::merge($a, $b);
 | ||
|         $expected = [
 | ||
|             'name' => 'Yii',
 | ||
|             'version' => '1.1',
 | ||
|             'features' => [
 | ||
|                 'mvc',
 | ||
|                 'gii',
 | ||
|             ],
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMergeWithReplace()
 | ||
|     {
 | ||
|         $a = [
 | ||
|             'name' => 'Yii',
 | ||
|             'version' => '1.0',
 | ||
|             'options' => [
 | ||
|                 'namespace' => false,
 | ||
|                 'unittest' => false,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'mvc',
 | ||
|             ],
 | ||
|         ];
 | ||
|         $b = [
 | ||
|             'version' => '1.1',
 | ||
|             'options' => [
 | ||
|                 'unittest' => true,
 | ||
|             ],
 | ||
|             'features' => new \yii\helpers\ReplaceArrayValue([
 | ||
|                 'gii',
 | ||
|             ]),
 | ||
|         ];
 | ||
| 
 | ||
|         $result = ArrayHelper::merge($a, $b);
 | ||
|         $expected = [
 | ||
|             'name' => 'Yii',
 | ||
|             'version' => '1.1',
 | ||
|             'options' => [
 | ||
|                 'namespace' => false,
 | ||
|                 'unittest' => true,
 | ||
|             ],
 | ||
|             'features' => [
 | ||
|                 'gii',
 | ||
|             ],
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMergeWithNullValues()
 | ||
|     {
 | ||
|         $a = [
 | ||
|             'firstValue',
 | ||
|             null,
 | ||
|         ];
 | ||
|         $b = [
 | ||
|             'secondValue',
 | ||
|             'thirdValue',
 | ||
|         ];
 | ||
| 
 | ||
|         $result = ArrayHelper::merge($a, $b);
 | ||
|         $expected = [
 | ||
|             'firstValue',
 | ||
|             null,
 | ||
|             'secondValue',
 | ||
|             'thirdValue',
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMergeEmpty()
 | ||
|     {
 | ||
|         $this->assertEquals([], ArrayHelper::merge([], []));
 | ||
|         $this->assertEquals([], ArrayHelper::merge([], [], []));
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @see https://github.com/yiisoft/yii2/pull/11549
 | ||
|      */
 | ||
|     public function testGetValueWithFloatKeys()
 | ||
|     {
 | ||
|         if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
 | ||
|             $this->markTestSkipped('Using floats as array key is deprecated.');
 | ||
|         }
 | ||
| 
 | ||
|         $array = [];
 | ||
|         $array[1.1] = 'some value';
 | ||
|         $array[2.1] = null;
 | ||
| 
 | ||
|         $result = ArrayHelper::getValue($array, 1.2);
 | ||
|         $this->assertEquals('some value', $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::getValue($array, 2.2);
 | ||
|         $this->assertNull($result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testIndex()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             ['id' => '123', 'data' => 'abc'],
 | ||
|             ['id' => '345', 'data' => 'def'],
 | ||
|             ['id' => '345', 'data' => 'ghi'],
 | ||
|         ];
 | ||
|         $result = ArrayHelper::index($array, 'id');
 | ||
|         $this->assertEquals([
 | ||
|             '123' => ['id' => '123', 'data' => 'abc'],
 | ||
|             '345' => ['id' => '345', 'data' => 'ghi'],
 | ||
|         ], $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::index($array, function ($element) {
 | ||
|             return $element['data'];
 | ||
|         });
 | ||
|         $this->assertEquals([
 | ||
|             'abc' => ['id' => '123', 'data' => 'abc'],
 | ||
|             'def' => ['id' => '345', 'data' => 'def'],
 | ||
|             'ghi' => ['id' => '345', 'data' => 'ghi'],
 | ||
|         ], $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::index($array, null);
 | ||
|         $this->assertEquals([], $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::index($array, function ($element) {
 | ||
|             return null;
 | ||
|         });
 | ||
|         $this->assertEquals([], $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::index($array, function ($element) {
 | ||
|             return $element['id'] == '345' ? null : $element['id'];
 | ||
|         });
 | ||
|         $this->assertEquals([
 | ||
|             '123' => ['id' => '123', 'data' => 'abc'],
 | ||
|         ], $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testIndexGroupBy()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             ['id' => '123', 'data' => 'abc'],
 | ||
|             ['id' => '345', 'data' => 'def'],
 | ||
|             ['id' => '345', 'data' => 'ghi'],
 | ||
|         ];
 | ||
| 
 | ||
|         $expected = [
 | ||
|             '123' => [
 | ||
|                 ['id' => '123', 'data' => 'abc'],
 | ||
|             ],
 | ||
|             '345' => [
 | ||
|                 ['id' => '345', 'data' => 'def'],
 | ||
|                 ['id' => '345', 'data' => 'ghi'],
 | ||
|             ],
 | ||
|         ];
 | ||
|         $result = ArrayHelper::index($array, null, ['id']);
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|         $result = ArrayHelper::index($array, null, 'id');
 | ||
|         $this->assertEquals($expected, $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::index($array, null, ['id', 'data']);
 | ||
|         $this->assertEquals([
 | ||
|             '123' => [
 | ||
|                 'abc' => [
 | ||
|                     ['id' => '123', 'data' => 'abc'],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             '345' => [
 | ||
|                 'def' => [
 | ||
|                     ['id' => '345', 'data' => 'def'],
 | ||
|                 ],
 | ||
|                 'ghi' => [
 | ||
|                     ['id' => '345', 'data' => 'ghi'],
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], $result);
 | ||
| 
 | ||
|         $expected = [
 | ||
|             '123' => [
 | ||
|                 'abc' => ['id' => '123', 'data' => 'abc'],
 | ||
|             ],
 | ||
|             '345' => [
 | ||
|                 'def' => ['id' => '345', 'data' => 'def'],
 | ||
|                 'ghi' => ['id' => '345', 'data' => 'ghi'],
 | ||
|             ],
 | ||
|         ];
 | ||
|         $result = ArrayHelper::index($array, 'data', ['id']);
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|         $result = ArrayHelper::index($array, 'data', 'id');
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|         $result = ArrayHelper::index($array, function ($element) {
 | ||
|             return $element['data'];
 | ||
|         }, 'id');
 | ||
|         $this->assertEquals($expected, $result);
 | ||
| 
 | ||
|         $expected = [
 | ||
|             '123' => [
 | ||
|                 'abc' => [
 | ||
|                     'abc' => ['id' => '123', 'data' => 'abc'],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             '345' => [
 | ||
|                 'def' => [
 | ||
|                     'def' => ['id' => '345', 'data' => 'def'],
 | ||
|                 ],
 | ||
|                 'ghi' => [
 | ||
|                     'ghi' => ['id' => '345', 'data' => 'ghi'],
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ];
 | ||
|         $result = ArrayHelper::index($array, 'data', ['id', 'data']);
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|         $result = ArrayHelper::index($array, function ($element) {
 | ||
|             return $element['data'];
 | ||
|         }, ['id', 'data']);
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @see https://github.com/yiisoft/yii2/issues/11739
 | ||
|      */
 | ||
|     public function testIndexFloat()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             ['id' => 1e6],
 | ||
|             ['id' => 1e32],
 | ||
|             ['id' => 1e64],
 | ||
|             ['id' => 1465540807.522109],
 | ||
|         ];
 | ||
| 
 | ||
|         $expected = [
 | ||
|             '1000000' => ['id' => 1e6],
 | ||
|             '1.0E+32' => ['id' => 1e32],
 | ||
|             '1.0E+64' => ['id' => 1e64],
 | ||
|             '1465540807.5221' => ['id' => 1465540807.522109],
 | ||
|         ];
 | ||
| 
 | ||
|         $result = ArrayHelper::index($array, 'id');
 | ||
| 
 | ||
|         $this->assertEquals($expected, $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testGetColumn()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'a' => ['id' => '123', 'data' => 'abc'],
 | ||
|             'b' => ['id' => '345', 'data' => 'def'],
 | ||
|         ];
 | ||
|         $result = ArrayHelper::getColumn($array, 'id');
 | ||
|         $this->assertEquals(['a' => '123', 'b' => '345'], $result);
 | ||
|         $result = ArrayHelper::getColumn($array, 'id', false);
 | ||
|         $this->assertEquals(['123', '345'], $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::getColumn($array, function ($element) {
 | ||
|             return $element['data'];
 | ||
|         });
 | ||
|         $this->assertEquals(['a' => 'abc', 'b' => 'def'], $result);
 | ||
|         $result = ArrayHelper::getColumn($array, function ($element) {
 | ||
|             return $element['data'];
 | ||
|         }, false);
 | ||
|         $this->assertEquals(['abc', 'def'], $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testMap()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             ['id' => '123', 'name' => 'aaa', 'class' => 'x'],
 | ||
|             ['id' => '124', 'name' => 'bbb', 'class' => 'x'],
 | ||
|             ['id' => '345', 'name' => 'ccc', 'class' => 'y'],
 | ||
|         ];
 | ||
| 
 | ||
|         $result = ArrayHelper::map($array, 'id', 'name');
 | ||
|         $this->assertEquals([
 | ||
|             '123' => 'aaa',
 | ||
|             '124' => 'bbb',
 | ||
|             '345' => 'ccc',
 | ||
|         ], $result);
 | ||
| 
 | ||
|         $result = ArrayHelper::map($array, 'id', 'name', 'class');
 | ||
|         $this->assertEquals([
 | ||
|             'x' => [
 | ||
|                 '123' => 'aaa',
 | ||
|                 '124' => 'bbb',
 | ||
|             ],
 | ||
|             'y' => [
 | ||
|                 '345' => 'ccc',
 | ||
|             ],
 | ||
|         ], $result);
 | ||
|     }
 | ||
| 
 | ||
|     public function testKeyExists()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             '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));
 | ||
| 
 | ||
|         $this->assertTrue(ArrayHelper::keyExists('a', $array, false));
 | ||
|         $this->assertTrue(ArrayHelper::keyExists('b', $array, false));
 | ||
|         $this->assertTrue(ArrayHelper::keyExists('B', $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()
 | ||
|     {
 | ||
|         $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));
 | ||
|     }
 | ||
| 
 | ||
|     public function testKeyExistsArrayAccessCaseInsensitiveThrowsError()
 | ||
|     {
 | ||
|         $this->expectException('yii\base\InvalidArgumentException');
 | ||
|         $this->expectExceptionMessage('Second parameter($array) cannot be ArrayAccess in case insensitive mode');
 | ||
|         $array = new TraversableArrayAccessibleObject([
 | ||
|             'a' => 1,
 | ||
|             'B' => 2,
 | ||
|         ]);
 | ||
| 
 | ||
|         ArrayHelper::keyExists('a', $array, false);
 | ||
|     }
 | ||
| 
 | ||
|     public function valueProvider()
 | ||
|     {
 | ||
|         return [
 | ||
|             ['name', 'test'],
 | ||
|             ['noname', null],
 | ||
|             ['noname', 'test', 'test'],
 | ||
|             ['post.id', 5],
 | ||
|             ['post.id', 5, 'test'],
 | ||
|             ['nopost.id', null],
 | ||
|             ['nopost.id', 'test', 'test'],
 | ||
|             ['post.author.name', 'cebe'],
 | ||
|             ['post.author.noname', null],
 | ||
|             ['post.author.noname', 'test', 'test'],
 | ||
|             ['post.author.profile.title', '1337'],
 | ||
|             ['admin.firstname', 'Qiang'],
 | ||
|             ['admin.firstname', 'Qiang', 'test'],
 | ||
|             ['admin.lastname', 'Xue'],
 | ||
|             [
 | ||
|                 function ($array, $defaultValue) {
 | ||
|                     return $array['date'] . $defaultValue;
 | ||
|                 },
 | ||
|                 '31-12-2113test',
 | ||
|                 'test',
 | ||
|             ],
 | ||
|             [['version', '1.0', 'status'], 'released'],
 | ||
|             [['version', '1.0', 'date'], 'defaultValue', 'defaultValue'],
 | ||
|         ];
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @dataProvider valueProvider
 | ||
|      *
 | ||
|      * @param $key
 | ||
|      * @param $expected
 | ||
|      * @param null $default
 | ||
|      */
 | ||
|     public function testGetValue($key, $expected, $default = null)
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'name' => 'test',
 | ||
|             'date' => '31-12-2113',
 | ||
|             'post' => [
 | ||
|                 'id' => 5,
 | ||
|                 'author' => [
 | ||
|                     'name' => 'cebe',
 | ||
|                     'profile' => [
 | ||
|                         'title' => '1337',
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             'admin.firstname' => 'Qiang',
 | ||
|             'admin.lastname' => 'Xue',
 | ||
|             'admin' => [
 | ||
|                 'lastname' => 'cebe',
 | ||
|             ],
 | ||
|             'version' => [
 | ||
|                 '1.0' => [
 | ||
|                     'status' => 'released',
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($expected, ArrayHelper::getValue($array, $key, $default));
 | ||
|     }
 | ||
| 
 | ||
|     public function testGetValueObjects()
 | ||
|     {
 | ||
|         $arrayObject = new \ArrayObject(['id' => 23], \ArrayObject::ARRAY_AS_PROPS);
 | ||
|         $this->assertEquals(23, ArrayHelper::getValue($arrayObject, 'id'));
 | ||
| 
 | ||
|         $object = new Post1();
 | ||
|         $this->assertEquals(23, ArrayHelper::getValue($object, 'id'));
 | ||
|     }
 | ||
| 
 | ||
|     public function testGetValueNonexistingProperties1()
 | ||
|     {
 | ||
|         if (PHP_VERSION_ID < 80000) {
 | ||
|             $this->expectException('PHPUnit_Framework_Error_Notice');
 | ||
|         } else {
 | ||
|             $this->expectException('PHPUnit_Framework_Error_Warning');
 | ||
|         }
 | ||
|         $object = new Post1();
 | ||
|         ArrayHelper::getValue($object, 'nonExisting');
 | ||
|     }
 | ||
| 
 | ||
|     public function testGetValueNonexistingPropertiesForArrayObject()
 | ||
|     {
 | ||
|         $arrayObject = new \ArrayObject(['id' => 23], \ArrayObject::ARRAY_AS_PROPS);
 | ||
|         $this->assertNull(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()]].
 | ||
|      * @return array test data
 | ||
|      */
 | ||
|     public function dataProviderSetValue()
 | ||
|     {
 | ||
|         return [
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key2' => 'val2',
 | ||
|                 ],
 | ||
|                 'key', 'val',
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key2' => 'val2',
 | ||
|                     'key' => 'val',
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key2' => 'val2',
 | ||
|                 ],
 | ||
|                 'key2', 'val',
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key2' => 'val',
 | ||
|                 ],
 | ||
|             ],
 | ||
| 
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                 ],
 | ||
|                 'key.in', 'val',
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key' => ['in' => 'val'],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => 'val1',
 | ||
|                 ],
 | ||
|                 'key.in', 'val',
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'val1',
 | ||
|                         'in' => 'val',
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => 'val1',
 | ||
|                 ],
 | ||
|                 'key', ['in' => 'val'],
 | ||
|                 [
 | ||
|                     'key' => ['in' => 'val'],
 | ||
|                 ],
 | ||
|             ],
 | ||
| 
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                 ],
 | ||
|                 'key.in.0', 'val',
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key' => [
 | ||
|                         'in' => ['val'],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
| 
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                 ],
 | ||
|                 'key.in.arr', 'val',
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'arr' => 'val',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                 ],
 | ||
|                 'key.in.arr', ['val'],
 | ||
|                 [
 | ||
|                     'key1' => 'val1',
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'arr' => ['val'],
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => ['val1'],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|                 'key.in.arr', 'val',
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'val1',
 | ||
|                             'arr' => 'val',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
| 
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => ['in' => 'val1'],
 | ||
|                 ],
 | ||
|                 'key.in.arr', ['val'],
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'val1',
 | ||
|                             'arr' => ['val'],
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'val1',
 | ||
|                             'key' => 'val',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|                 'key.in.0', ['arr' => 'val'],
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             ['arr' => 'val'],
 | ||
|                             'key' => 'val',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'val1',
 | ||
|                             'key' => 'val',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|                 'key.in', ['arr' => 'val'],
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => ['arr' => 'val'],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'key' => 'val',
 | ||
|                             'data' => [
 | ||
|                                 'attr1',
 | ||
|                                 'attr2',
 | ||
|                                 'attr3',
 | ||
|                             ],
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|                 'key.in.schema', 'array',
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in' => [
 | ||
|                             'key' => 'val',
 | ||
|                             'schema' => 'array',
 | ||
|                             'data' => [
 | ||
|                                 'attr1',
 | ||
|                                 'attr2',
 | ||
|                                 'attr3',
 | ||
|                             ],
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in.array' => [
 | ||
|                             'key' => 'val',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|                 ['key', 'in.array', 'ok.schema'], 'array',
 | ||
|                 [
 | ||
|                     'key' => [
 | ||
|                         'in.array' => [
 | ||
|                             'key' => 'val',
 | ||
|                             'ok.schema' => 'array',
 | ||
|                         ],
 | ||
|                     ],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             [
 | ||
|                 [
 | ||
|                     'key' => ['val'],
 | ||
|                 ],
 | ||
|                 null, 'data',
 | ||
|                 'data',
 | ||
|             ],
 | ||
|         ];
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @dataProvider dataProviderSetValue
 | ||
|      *
 | ||
|      * @param array $array_input
 | ||
|      * @param string|array|null $key
 | ||
|      * @param mixed $value
 | ||
|      * @param mixed $expected
 | ||
|      */
 | ||
|     public function testSetValue($array_input, $key, $value, $expected)
 | ||
|     {
 | ||
|         ArrayHelper::setValue($array_input, $key, $value);
 | ||
|         $this->assertEquals($expected, $array_input);
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsAssociative()
 | ||
|     {
 | ||
|         $this->assertFalse(ArrayHelper::isAssociative('test'));
 | ||
|         $this->assertFalse(ArrayHelper::isAssociative([]));
 | ||
|         $this->assertFalse(ArrayHelper::isAssociative([1, 2, 3]));
 | ||
|         $this->assertFalse(ArrayHelper::isAssociative([1], false));
 | ||
|         $this->assertTrue(ArrayHelper::isAssociative(['name' => 1, 'value' => 'test']));
 | ||
|         $this->assertFalse(ArrayHelper::isAssociative(['name' => 1, 'value' => 'test', 3]));
 | ||
|         $this->assertTrue(ArrayHelper::isAssociative(['name' => 1, 'value' => 'test', 3], false));
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsIndexed()
 | ||
|     {
 | ||
|         $this->assertFalse(ArrayHelper::isIndexed('test'));
 | ||
|         $this->assertTrue(ArrayHelper::isIndexed([]));
 | ||
|         $this->assertTrue(ArrayHelper::isIndexed([1, 2, 3]));
 | ||
|         $this->assertTrue(ArrayHelper::isIndexed([2 => 'a', 3 => 'b']));
 | ||
|         $this->assertFalse(ArrayHelper::isIndexed([2 => 'a', 3 => 'b'], true));
 | ||
|         $this->assertFalse(ArrayHelper::isIndexed(['a' => 'b'], false));
 | ||
|     }
 | ||
| 
 | ||
|     public function testHtmlEncode()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'abc' => '123',
 | ||
|             '<' => '>',
 | ||
|             'cde' => false,
 | ||
|             3 => 'blank',
 | ||
|             [
 | ||
|                 '<>' => 'a<>b',
 | ||
|                 '23' => true,
 | ||
|             ],
 | ||
|             'invalid' => "a\x80b",
 | ||
|         ];
 | ||
|         $this->assertEquals([
 | ||
|             'abc' => '123',
 | ||
|             '<' => '>',
 | ||
|             'cde' => false,
 | ||
|             3 => 'blank',
 | ||
|             [
 | ||
|                 '<>' => 'a<>b',
 | ||
|                 '23' => true,
 | ||
|             ],
 | ||
|             'invalid' => 'a<>b',
 | ||
|         ], ArrayHelper::htmlEncode($array));
 | ||
|         $this->assertEquals([
 | ||
|             'abc' => '123',
 | ||
|             '<' => '>',
 | ||
|             'cde' => false,
 | ||
|             3 => 'blank',
 | ||
|             [
 | ||
|                 '<>' => 'a<>b',
 | ||
|                 '23' => true,
 | ||
|             ],
 | ||
|             'invalid' => 'a<>b',
 | ||
|         ], ArrayHelper::htmlEncode($array, false));
 | ||
|     }
 | ||
| 
 | ||
|     public function testHtmlDecode()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'abc' => '123',
 | ||
|             '<' => '>',
 | ||
|             'cde' => false,
 | ||
|             3 => 'blank',
 | ||
|             [
 | ||
|                 '<>' => 'a<>b',
 | ||
|                 '<a>' => '<a href="index.php?a=1&b=2">link</a>',
 | ||
|                 '23' => true,
 | ||
|             ],
 | ||
|         ];
 | ||
| 
 | ||
|         $expected = [
 | ||
|             'abc' => '123',
 | ||
|             '<' => '>',
 | ||
|             'cde' => false,
 | ||
|             3 => 'blank',
 | ||
|             [
 | ||
|                 '<>' => 'a<>b',
 | ||
|                 '<a>' => '<a href="index.php?a=1&b=2">link</a>',
 | ||
|                 '23' => true,
 | ||
|             ],
 | ||
|         ];
 | ||
|         $this->assertEquals($expected, ArrayHelper::htmlDecode($array));
 | ||
|         $expected = [
 | ||
|             'abc' => '123',
 | ||
|             '<' => '>',
 | ||
|             'cde' => false,
 | ||
|             3 => 'blank',
 | ||
|             [
 | ||
|                 '<>' => 'a<>b',
 | ||
|                 '<a>' => '<a href="index.php?a=1&b=2">link</a>',
 | ||
|                 '23' => true,
 | ||
|             ],
 | ||
|         ];
 | ||
|         $this->assertEquals($expected, ArrayHelper::htmlDecode($array, false));
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsIn()
 | ||
|     {
 | ||
|         $this->assertTrue(ArrayHelper::isIn('a', new \ArrayObject(['a', 'b'])));
 | ||
|         $this->assertTrue(ArrayHelper::isIn('a', ['a', 'b']));
 | ||
| 
 | ||
|         $this->assertTrue(ArrayHelper::isIn('1', new \ArrayObject([1, 'b'])));
 | ||
|         $this->assertTrue(ArrayHelper::isIn('1', [1, 'b']));
 | ||
| 
 | ||
|         $this->assertFalse(ArrayHelper::isIn('1', new \ArrayObject([1, 'b']), true));
 | ||
|         $this->assertFalse(ArrayHelper::isIn('1', [1, 'b'], true));
 | ||
| 
 | ||
|         $this->assertTrue(ArrayHelper::isIn(['a'], new \ArrayObject([['a'], 'b'])));
 | ||
|         $this->assertFalse(ArrayHelper::isIn('a', new \ArrayObject([['a'], 'b'])));
 | ||
|         $this->assertFalse(ArrayHelper::isIn('a', [['a'], 'b']));
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsInStrict()
 | ||
|     {
 | ||
|         // strict comparison
 | ||
|         $this->assertTrue(ArrayHelper::isIn(1, new \ArrayObject([1, 'a']), true));
 | ||
|         $this->assertTrue(ArrayHelper::isIn(1, [1, 'a'], true));
 | ||
| 
 | ||
|         $this->assertFalse(ArrayHelper::isIn('1', new \ArrayObject([1, 'a']), true));
 | ||
|         $this->assertFalse(ArrayHelper::isIn('1', [1, 'a'], true));
 | ||
|     }
 | ||
| 
 | ||
|     public function testInException()
 | ||
|     {
 | ||
|         $this->expectException('yii\base\InvalidParamException');
 | ||
|         $this->expectExceptionMessage('Argument $haystack must be an array or implement Traversable');
 | ||
|         ArrayHelper::isIn('value', null);
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsSubset()
 | ||
|     {
 | ||
|         $this->assertTrue(ArrayHelper::isSubset(['a'], new \ArrayObject(['a', 'b'])));
 | ||
|         $this->assertTrue(ArrayHelper::isSubset(new \ArrayObject(['a']), ['a', 'b']));
 | ||
| 
 | ||
|         $this->assertTrue(ArrayHelper::isSubset([1], new \ArrayObject(['1', 'b'])));
 | ||
|         $this->assertTrue(ArrayHelper::isSubset(new \ArrayObject([1]), ['1', 'b']));
 | ||
| 
 | ||
|         $this->assertFalse(ArrayHelper::isSubset([1], new \ArrayObject(['1', 'b']), true));
 | ||
|         $this->assertFalse(ArrayHelper::isSubset(new \ArrayObject([1]), ['1', 'b'], true));
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsSubsetException()
 | ||
|     {
 | ||
|         $this->expectException('yii\base\InvalidParamException');
 | ||
|         $this->expectExceptionMessage('Argument $needles must be an array or implement Traversable');
 | ||
|         ArrayHelper::isSubset('a', new \ArrayObject(['a', 'b']));
 | ||
|     }
 | ||
| 
 | ||
|     public function testIsArray()
 | ||
|     {
 | ||
|         $this->assertTrue(ArrayHelper::isTraversable(['a']));
 | ||
|         $this->assertTrue(ArrayHelper::isTraversable(new \ArrayObject(['1'])));
 | ||
|         $this->assertFalse(ArrayHelper::isTraversable(new \stdClass()));
 | ||
|         $this->assertFalse(ArrayHelper::isTraversable('A,B,C'));
 | ||
|         $this->assertFalse(ArrayHelper::isTraversable(12));
 | ||
|         $this->assertFalse(ArrayHelper::isTraversable(false));
 | ||
|         $this->assertFalse(ArrayHelper::isTraversable(null));
 | ||
|     }
 | ||
| 
 | ||
|     public function testFilter()
 | ||
|     {
 | ||
|         $array = [
 | ||
|             'A' => [
 | ||
|                 'B' => 1,
 | ||
|                 'C' => 2,
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|             'G' => 1,
 | ||
|         ];
 | ||
| 
 | ||
|         //Include tests
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'B' => 1,
 | ||
|                 'C' => 2,
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'B' => 1,
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A.B']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A.D']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A.D.E']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'B' => 1,
 | ||
|                 'C' => 2,
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|             'G' => 1,
 | ||
|         ], ArrayHelper::filter($array, ['A', 'G']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                 ],
 | ||
|             ],
 | ||
|             'G' => 1,
 | ||
|         ], ArrayHelper::filter($array, ['A.D.E', 'G']));
 | ||
| 
 | ||
|         //Exclude (combined with include) tests
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'C' => 2,
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A', '!A.B']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'C' => 2,
 | ||
|                 'D' => [
 | ||
|                     'E' => 1,
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['!A.B', 'A']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'B' => 1,
 | ||
|                 'C' => 2,
 | ||
|                 'D' => [
 | ||
|                     'F' => 2,
 | ||
|                 ],
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A', '!A.D.E']));
 | ||
|         $this->assertEquals([
 | ||
|             'A' => [
 | ||
|                 'B' => 1,
 | ||
|                 'C' => 2,
 | ||
|             ],
 | ||
|         ], ArrayHelper::filter($array, ['A', '!A.D']));
 | ||
|         $this->assertEquals([
 | ||
|             'G' => 1
 | ||
|         ], ArrayHelper::filter($array, ['G', '!Z', '!X.A']));
 | ||
| 
 | ||
|         //Non existing keys tests
 | ||
|         $this->assertEquals([], ArrayHelper::filter($array, ['X']));
 | ||
|         $this->assertEquals([], ArrayHelper::filter($array, ['X.Y']));
 | ||
|         $this->assertEquals([], ArrayHelper::filter($array, ['X.Y.Z']));
 | ||
|         $this->assertEquals([], ArrayHelper::filter($array, ['A.X']));
 | ||
| 
 | ||
|         //Values that evaluate to `true` with `empty()` tests
 | ||
|         $tmp = [
 | ||
|             'a' => 0,
 | ||
|             'b' => '',
 | ||
|             'c' => false,
 | ||
|             'd' => null,
 | ||
|             'e' => true,
 | ||
|         ];
 | ||
| 
 | ||
|         $this->assertEquals($tmp, ArrayHelper::filter($tmp, array_keys($tmp)));
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @see https://github.com/yiisoft/yii2/issues/18395
 | ||
|      */
 | ||
|     public function testFilterForIntegerKeys()
 | ||
|     {
 | ||
|         $array = ['a', 'b', ['c', 'd']];
 | ||
| 
 | ||
|         // to make sure order is changed test it encoded
 | ||
|         $this->assertEquals('{"1":"b","0":"a"}', json_encode(ArrayHelper::filter($array, [1, 0])));
 | ||
|         $this->assertEquals([2 => ['c']], ArrayHelper::filter($array, ['2.0']));
 | ||
|         $this->assertEquals([2 => [1 => 'd']], ArrayHelper::filter($array, [2, '!2.0']));
 | ||
|     }
 | ||
| 
 | ||
|     public function testFilterWithInvalidValues()
 | ||
|     {
 | ||
|         $array = ['a' => 'b'];
 | ||
| 
 | ||
|         $this->assertEquals([], ArrayHelper::filter($array, [new \stdClass()]));
 | ||
|         $this->assertEquals([], ArrayHelper::filter($array, [['a']]));
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @see https://github.com/yiisoft/yii2/issues/18086
 | ||
|      */
 | ||
|     public function testArrayAccessWithPublicProperty()
 | ||
|     {
 | ||
|         $data = new ArrayAccessibleObject(['value' => 123]);
 | ||
| 
 | ||
|         $this->assertEquals(123, ArrayHelper::getValue($data, 'value'));
 | ||
|         $this->assertEquals('bar1', ArrayHelper::getValue($data, 'name'));
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * https://github.com/yiisoft/yii2/commit/35fb9c624893855317e5fe52e6a21f6518a9a31c changed the way
 | ||
|      * ArrayHelper works with existing object properties in case of ArrayAccess.
 | ||
|      */
 | ||
|     public function testArrayAccessWithMagicProperty()
 | ||
|     {
 | ||
|         $model = new MagicModel();
 | ||
|         $this->assertEquals(42, ArrayHelper::getValue($model, 'magic'));
 | ||
|         $this->assertEquals('ta-da', ArrayHelper::getValue($model, 'moreMagic'));
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * @dataProvider dataProviderRecursiveSort
 | ||
|      *
 | ||
|      * @return void
 | ||
|      */
 | ||
|     public function testRecursiveSort($expected_result, $input_array)
 | ||
|     {
 | ||
|         $actual = ArrayHelper::recursiveSort($input_array);
 | ||
|         $this->assertEquals($expected_result, $actual);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Data provider for [[testRecursiveSort()]].
 | ||
|      * @return array test data
 | ||
|      */
 | ||
|     public function dataProviderRecursiveSort()
 | ||
|     {
 | ||
|         return [
 | ||
|             //Normal index array
 | ||
|             [
 | ||
|                 [1, 2, 3, 4],
 | ||
|                 [4, 1, 3, 2]
 | ||
|             ],
 | ||
|             //Normal associative array
 | ||
|             [
 | ||
|                 ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4],
 | ||
|                 ['b' => 2, 'a' => 1, 'd' => 4, 'c' => 3],
 | ||
|             ],
 | ||
|             //Normal index array
 | ||
|             [
 | ||
|                 [1, 2, 3, 4],
 | ||
|                 [4, 1, 3, 2]
 | ||
|             ],
 | ||
|             //Multidimensional associative array
 | ||
|             [
 | ||
|                 [
 | ||
|                     'a' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4],
 | ||
|                     'b' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4],
 | ||
|                     'c' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4],
 | ||
|                     'd' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4],
 | ||
|                 ],
 | ||
|                 [
 | ||
|                     'b' => ['a' => 1, 'd' => 4, 'b' => 2, 'c' => 3],
 | ||
|                     'd' => ['b' => 2, 'c' => 3, 'a' => 1, 'd' => 4],
 | ||
|                     'c' => ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 2],
 | ||
|                     'a' => ['d' => 4, 'b' => 2, 'c' => 3, 'a' => 1],
 | ||
|                 ],
 | ||
|             ],
 | ||
|             //Multidimensional associative array
 | ||
|             [
 | ||
|                 [
 | ||
|                     'a' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4]],
 | ||
|                     'b' => ['a' => 1, 'b' => 2, 'c' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], 'd' => 4],
 | ||
|                     'c' => ['a' => 1, 'b' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], 'c' => 3, 'd' => 4],
 | ||
|                     'd' => ['a' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], 'b' => 2, 'c' => 3, 'd' => 4],
 | ||
|                 ],
 | ||
|                 [
 | ||
|                     'b' => ['a' => 1, 'd' => 4, 'b' => 2, 'c' => ['b' => 2, 'c' => 3, 'a' => 1, 'd' => 4]],
 | ||
|                     'd' => ['b' => 2, 'c' => 3, 'a' => ['a' => 1, 'd' => 4, 'b' => 2, 'c' => 3], 'd' => 4],
 | ||
|                     'c' => ['c' => 3, 'a' => 1, 'd' => 4, 'b' => ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 2]],
 | ||
|                     'a' => ['d' => ['d' => 4, 'b' => 2, 'c' => 3, 'a' => 1], 'b' => 2, 'c' => 3, 'a' => 1],
 | ||
|                 ]
 | ||
|             ],
 | ||
|         ];
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| class Post1
 | ||
| {
 | ||
|     public $id = 23;
 | ||
|     public $title = 'tt';
 | ||
| }
 | ||
| 
 | ||
| class Post2 extends BaseObject
 | ||
| {
 | ||
|     public $id = 123;
 | ||
|     public $content = 'test';
 | ||
|     private $secret = 's';
 | ||
|     public function getSecret()
 | ||
|     {
 | ||
|         return $this->secret;
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| class Post3 extends BaseObject
 | ||
| {
 | ||
|     public $id = 33;
 | ||
|     /** @var BaseObject */
 | ||
|     public $subObject;
 | ||
| 
 | ||
|     public function init()
 | ||
|     {
 | ||
|         $this->subObject = new Post2();
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| class ArrayAccessibleObject implements ArrayAccess
 | ||
| {
 | ||
|     public $name = 'bar1';
 | ||
|     protected $container = [];
 | ||
| 
 | ||
|     public function __construct($container)
 | ||
|     {
 | ||
|         $this->container = $container;
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function offsetSet($offset, $value)
 | ||
|     {
 | ||
|         if (is_null($offset)) {
 | ||
|             $this->container[] = $value;
 | ||
|         } else {
 | ||
|             $this->container[$offset] = $value;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function offsetExists($offset)
 | ||
|     {
 | ||
|         return array_key_exists($offset, $this->container);
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function offsetUnset($offset)
 | ||
|     {
 | ||
|         unset($this->container[$offset]);
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     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;
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function rewind()
 | ||
|     {
 | ||
|         $this->position = 0;
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function current()
 | ||
|     {
 | ||
|         return $this->offsetGet($this->getContainerKey($this->position));
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function key()
 | ||
|     {
 | ||
|         return $this->getContainerKey($this->position);
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function next()
 | ||
|     {
 | ||
|         ++$this->position;
 | ||
|     }
 | ||
| 
 | ||
|     #[\ReturnTypeWillChange]
 | ||
|     public function valid()
 | ||
|     {
 | ||
|         $key = $this->getContainerKey($this->position);
 | ||
|         return !(!$key || !$this->offsetExists($key));
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| class MagicModel extends Model
 | ||
| {
 | ||
|     protected $magic;
 | ||
| 
 | ||
|     public function getMagic()
 | ||
|     {
 | ||
|         return 42;
 | ||
|     }
 | ||
| 
 | ||
|     private $moreMagic;
 | ||
| 
 | ||
|     public function getMoreMagic()
 | ||
|     {
 | ||
|         return 'ta-da';
 | ||
|     }
 | ||
| }
 | 
