From 1dfe00c0db8f9ca5a9204fe769ae88d85581da02 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 6 Apr 2025 15:40:04 +0700 Subject: [PATCH 1/6] Refactor `DbArrayHelper` --- src/Helper/DbArrayHelper.php | 180 ++++++++++++-------- src/Query/Query.php | 19 +-- tests/Db/Helper/DbArrayHelperTest.php | 81 ++++----- tests/Provider/DbArrayHelperProvider.php | 205 ++++++++++++++++++----- 4 files changed, 304 insertions(+), 181 deletions(-) diff --git a/src/Helper/DbArrayHelper.php b/src/Helper/DbArrayHelper.php index 209282855..b0a9dff2f 100644 --- a/src/Helper/DbArrayHelper.php +++ b/src/Helper/DbArrayHelper.php @@ -24,52 +24,20 @@ * Array manipulation methods. * * @psalm-import-type IndexBy from QueryInterface + * @psalm-import-type ResultCallback from QueryInterface */ final class DbArrayHelper { /** - * Indexes and/or groups the array according to a specified key. - * - * The input should be either a multidimensional array or an array of objects. - * - * The $key can be either a key name of the sub-array, a property name of an object, or an anonymous function that - * must return the value that will be used as a key. - * - * $groups is an array of keys, that will be used to group the input array into one or more sub-arrays based on keys - * specified. - * - * If the `$key` is specified as `null` or a value of an element corresponding to the key is `null` in addition - * to `$groups` not specified then the element is discarded. + * Arranges the array of rows according to specified keys. * * For example: * * ```php - * $array = [ - * ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], - * ['id' => '345', 'data' => 'def', 'device' => 'tablet'], - * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], - * ]; - * $result = DbArrayHelper::index($array, 'id'); + * $result = DbArrayHelper::arrange($rows, ['id']); * ``` * - * The result will be an associative array, where the key is the value of `id` attribute - * - * ```php - * [ - * '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], - * '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] - * // The second element of an original array is overwritten by the last element because of the same id - * ] - * ``` - * - * Passing `id` as a third argument will group `$array` by `id`: - * - * ```php - * $result = DbArrayHelper::index($array, null, 'id'); - * ``` - * - * The result will be a multidimensional array grouped by `id` on the first level, by `device` on the second level - * and indexed by `data` on the third level: + * The result will be a multidimensional array arranged by `id`: * * ```php * [ @@ -83,8 +51,14 @@ final class DbArrayHelper * ] * ``` * - * The result will be a multidimensional array grouped by `id` on the first level, by the `device` on the second one - * and indexed by the `data` on the third level: + * Another example: + * + * ```php + * $result = DbArrayHelper::arrange($rows, ['id', 'device'], 'data'); + * ``` + * + * The result will be a multidimensional array arranged by `id` on the first level, by `device` on the second level + * and indexed by `data` on the third level: * * ```php * [ @@ -104,35 +78,36 @@ final class DbArrayHelper * ] * ``` * - * @param array[] $array The array that needs to be indexed or arranged. - * @param Closure|string|null $indexBy The column name or anonymous function which result will be used to index the - * array. If the array does not have the key, the ordinal indexes will be used if `$arrangeBy` is not specified or - * a warning will be triggered if `$arrangeBy` is specified. + * @param array[] $rows The array of rows that needs to be arranged. * @param string[] $arrangeBy The array of keys that will be used to arrange the input array by one or more keys. + * @param Closure|string|null $indexBy The column name or anonymous function which result will be used to index the + * array. + * @param Closure|null $resultCallback The callback function that will be called with the result array. This can be + * used to modify the result before returning it. * - * @return array[] The indexed and/or arranged array. + * @return array[] The arranged array. * * @psalm-param IndexBy|null $indexBy * @psalm-suppress MixedArrayAssignment */ - public static function index(array $array, Closure|string|null $indexBy = null, array $arrangeBy = []): array - { - if (empty($array) || $indexBy === null && empty($arrangeBy)) { - return $array; + public static function arrange( + array $rows, + array $arrangeBy = [], + Closure|string|null $indexBy = null, + Closure|null $resultCallback = null, + ): array { + if (empty($rows)) { + return []; } if (empty($arrangeBy)) { - if (is_string($indexBy)) { - return array_column($array, null, $indexBy); - } - - return array_combine(array_map($indexBy, $array), $array); + return self::index($rows, $indexBy, $resultCallback); } - $result = []; + $arranged = []; - foreach ($array as $element) { - $lastArray = &$result; + foreach ($rows as $element) { + $lastArray = &$arranged; foreach ($arrangeBy as $group) { $value = (string) $element[$group]; @@ -144,23 +119,76 @@ public static function index(array $array, Closure|string|null $indexBy = null, $lastArray = &$lastArray[$value]; } - if ($indexBy === null) { - $lastArray[] = $element; - } else { - if (is_string($indexBy)) { - $value = $element[$indexBy]; - } else { - $value = $indexBy($element); - } + $lastArray[] = $element; + + unset($lastArray); + } + + if ($indexBy !== null || $resultCallback !== null) { + self::indexArranged($arranged, $indexBy, $resultCallback, count($arrangeBy)); + } + + return $arranged; + } - $lastArray[(string) $value] = $element; + /** + * Indexes the array of rows according to a specified key. + * + * For example: + * + * ```php + * $array = [ + * ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + * ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + * ]; + * $result = DbArrayHelper::index($array, 'id'); + * ``` + * + * The result will be an associative array, where the key is the value of `id` attribute + * + * ```php + * [ + * '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + * '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + * // The second element of an original array is overwritten by the last element because of the same id + * ] + * ``` + * + * @param array[] $rows The array of rows that needs to be indexed. + * @param Closure|string|null $indexBy The column name or anonymous function which result will be used to index the + * array. + * @param Closure|null $resultCallback The callback function that will be called with the result array. This can be + * used to modify the result before returning it. + * + * @return array[] The indexed array. + * + * @psalm-param IndexBy|null $indexBy + * @psalm-param ResultCallback|null $resultCallback + */ + public static function index( + array $rows, + Closure|string|null $indexBy = null, + Closure|null $resultCallback = null, + ): array { + if (empty($rows)) { + return []; + } + + if ($indexBy !== null) { + if (is_string($indexBy)) { + $indexes = array_column($rows, $indexBy); + } else { + $indexes = array_map($indexBy, $rows); } + } - unset($lastArray); + if ($resultCallback !== null) { + $rows = ($resultCallback)($rows); } - /** @var array[] $result */ - return $result; + /** @psalm-suppress MixedArgument */ + return !empty($indexes) ? array_combine($indexes, $rows) : $rows; } /** @@ -249,4 +277,22 @@ public static function toArray(array|object $object): array return get_object_vars($object); } + + /** + * Recursively indexes the arranged array. + */ + private static function indexArranged( + array &$arranged, + Closure|string|null $indexBy, + Closure|null $resultCallback, + int $depth, + ): void { + foreach ($arranged as &$rows) { + if ($depth > 1) { + self::indexArranged($rows, $indexBy, $resultCallback, $depth - 1); + } else { + $rows = self::index($rows, $indexBy, $resultCallback); + } + } + } } diff --git a/src/Query/Query.php b/src/Query/Query.php index 3e37d40b1..7034832c5 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -238,24 +238,7 @@ public function all(): array $rows = $this->createCommand()->queryAll(); - if (empty($rows)) { - return []; - } - - if ($this->indexBy !== null) { - if (is_string($this->indexBy)) { - $indexes = array_column($rows, $this->indexBy); - } else { - $indexes = array_map($this->indexBy, $rows); - } - } - - if ($this->resultCallback !== null) { - $rows = ($this->resultCallback)($rows); - } - - /** @psalm-suppress MixedArgument */ - return isset($indexes) ? array_combine($indexes, $rows) : $rows; + return DbArrayHelper::index($rows, $this->indexBy, $this->resultCallback); } public function average(string $sql): int|float|null|string diff --git a/tests/Db/Helper/DbArrayHelperTest.php b/tests/Db/Helper/DbArrayHelperTest.php index fc8bd4306..e11d59837 100644 --- a/tests/Db/Helper/DbArrayHelperTest.php +++ b/tests/Db/Helper/DbArrayHelperTest.php @@ -22,55 +22,36 @@ public function testIsAssociative(): void $this->assertFalse(DbArrayHelper::isAssociative([1])); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\DbArrayHelperProvider::index - */ + #[DataProviderExternal(DbArrayHelperProvider::class, 'index')] public function testIndex(array $rows): void { $this->assertSame($rows, DbArrayHelper::index($rows)); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\DbArrayHelperProvider::indexWithIndexBy - * @dataProvider \Yiisoft\Db\Tests\Provider\DbArrayHelperProvider::indexWithIncorrectIndexBy - * @dataProvider \Yiisoft\Db\Tests\Provider\DbArrayHelperProvider::indexWithIndexByClosure - */ - public function testPopulateWithIndexBy(Closure|string|null $indexBy, array $rows, array $expected): void - { - $this->assertSame($expected, DbArrayHelper::index($rows, $indexBy)); + #[DataProviderExternal(DbArrayHelperProvider::class, 'indexWithIndexBy')] + public function testPopulateWithIndexBy( + array $expected, + array $rows, + Closure|string|null $indexBy = null, + Closure|null $resultCallback = null, + ): void { + $this->assertSame($expected, DbArrayHelper::index($rows, $indexBy, $resultCallback)); } - /** - * @dataProvider \Yiisoft\Db\Tests\Provider\DbArrayHelperProvider::indexWithIndexBy - */ - public function testIndexWithIndexByWithObject(Closure|string|null $indexBy, array $rows, array $expected): void - { + #[DataProviderExternal(DbArrayHelperProvider::class, 'indexWithIndexBy')] + public function testIndexWithIndexByWithObject( + array $expected, + array $rows, + Closure|string|null $indexBy = null, + Closure|null $resultCallback = null, + ): void { $rows = json_decode(json_encode($rows)); - $populated = json_decode(json_encode(DbArrayHelper::index($rows, $indexBy)), true); + $populated = json_decode(json_encode(DbArrayHelper::index($rows, $indexBy, $resultCallback)), true); $this->assertSame($expected, $populated); } - public function testIndexWithNonExistingIndexBy(): void - { - $rows = [ - ['key' => 'value1'], - ['key' => 'value2'], - ]; - - $this->assertSame($rows, DbArrayHelper::index($rows, 'non-existing-key')); - - set_error_handler(static function (int $errno, string $errstr) { - restore_error_handler(); - throw new \Exception('E_WARNING: ' . $errstr, $errno); - }, E_WARNING); - - $this->expectExceptionMessage('E_WARNING: Undefined array key "non-existing-key"'); - - DbArrayHelper::index($rows, 'non-existing-key', ['key']); - } - - public function testIndexWithArrangeBy(): void + public function testArrangeWithNonExistingKey(): void { $rows = [ ['key' => 'value1'], @@ -84,24 +65,18 @@ public function testIndexWithArrangeBy(): void $this->expectExceptionMessage('E_WARNING: Undefined array key "non-existing-key"'); - DbArrayHelper::index($rows, null, ['non-existing-key']); + DbArrayHelper::arrange($rows, ['non-existing-key']); } - public function testIndexWithClosureIndexByAndArrangeBy(): void - { - $rows = [ - ['key' => 'value1'], - ['key' => 'value2'], - ]; - - $this->assertSame([ - 'value1' => [ - 'value1' => ['key' => 'value1'], - ], - 'value2' => [ - 'value2' => ['key' => 'value2'], - ], - ], DbArrayHelper::index($rows, fn ($row) => $row['key'], ['key'])); + #[DataProviderExternal(DbArrayHelperProvider::class, 'arrange')] + public function testArrange( + array $expected, + array $rows, + array $arrangeBy = [], + Closure|string|null $indexBy = null, + Closure|null $resultCallback = null + ): void { + $this->assertSame($expected, DbArrayHelper::arrange($rows, $arrangeBy, $indexBy, $resultCallback)); } #[DataProviderExternal(DbArrayHelperProvider::class, 'toArray')] diff --git a/tests/Provider/DbArrayHelperProvider.php b/tests/Provider/DbArrayHelperProvider.php index 2e1d77003..5c92dbeb3 100644 --- a/tests/Provider/DbArrayHelperProvider.php +++ b/tests/Provider/DbArrayHelperProvider.php @@ -6,6 +6,7 @@ use ArrayIterator; use Yiisoft\Db\Schema\Data\JsonLazyArray; +use function is_object; class DbArrayHelperProvider { @@ -27,28 +28,40 @@ public static function index(): array ]; } - public static function indexWithIndexByClosure(): array + public static function indexWithIndexBy(): array { + $resultCallback = fn (array $rows) => array_map( + fn (array|object $row) => ['key' => strtoupper(is_object($row) ? $row->key : $row['key'])], + $rows, + ); + return [ - [ - static fn($row) => $row['key'], + 'null key with empty rows' => [ + 'expected' => [], + 'rows' => [], + ], + 'null key' => [ + [ + ['key' => 'value1'], + ['key' => 'value2'], + ], [ ['key' => 'value1'], ['key' => 'value2'], ], + ], + 'correct key' => [ [ 'value1' => ['key' => 'value1'], 'value2' => ['key' => 'value2'], ], + [ + ['key' => 'value1'], + ['key' => 'value2'], + ], + 'key', ], - ]; - } - - public static function indexWithIncorrectIndexBy(): array - { - return [ - 'not existed key' => [ - 'incorrectKey', + 'null-key and composite.key' => [ [ ['table.key' => 'value1'], ['table.key' => 'value2'], @@ -58,71 +71,177 @@ public static function indexWithIncorrectIndexBy(): array ['table.key' => 'value2'], ], ], - ]; - } - - public static function indexWithIndexBy(): array - { - return [ - 'null key with empty rows' => [ - null, - 'rows' => [], - 'expected' => [], + 'key with space' => [ + [ + 'value1' => ['table key' => 'value1'], + 'value2' => ['table key' => 'value2'], + ], + [ + ['table key' => 'value1'], + ['table key' => 'value2'], + ], + 'table key', ], - 'null key' => [ - null, + 'composite-key and composite key' => [ + [ + 'value1' => ['table.key' => 'value1'], + 'value2' => ['table.key' => 'value2'], + ], + [ + ['table.key' => 'value1'], + ['table.key' => 'value2'], + ], + 'table.key', + ], + 'closure key' => [ + [ + 'value1' => ['key' => 'value1'], + 'value2' => ['key' => 'value2'], + ], [ ['key' => 'value1'], ['key' => 'value2'], ], + static fn ($row) => is_object($row) ? $row->key : $row['key'], + ], + 'not existed key' => [ + [ + ['table.key' => 'value1'], + ['table.key' => 'value2'], + ], + [ + ['table.key' => 'value1'], + ['table.key' => 'value2'], + ], + 'incorrectKey', + ], + 'key and resultCallback' => [ + [ + 'value1' => ['key' => 'VALUE1'], + 'value2' => ['key' => 'VALUE2'], + ], [ ['key' => 'value1'], ['key' => 'value2'], ], - ], - 'correct key' => [ 'key', + $resultCallback, + ], + 'null-key and resultCallback' => [ + [ + ['key' => 'VALUE1'], + ['key' => 'VALUE2'], + ], [ ['key' => 'value1'], ['key' => 'value2'], ], + null, + $resultCallback, + ], + ]; + } + + public static function arrange() + { + $rows = [ + ['key' => 'value1'], + ['key' => 'value2'], + ]; + $resultCallback = fn (array $rows) => array_map(fn (array $row) => ['key' => strtoupper($row['key'])], $rows); + + return [ + [ + 'expected' => [], + 'rows' => [], + ], + [ + $rows, + $rows, + ], + [ + [ + 'value1' => [['key' => 'value1']], + 'value2' => [['key' => 'value2']], + ], + $rows, + ['key'], + ], + [ [ 'value1' => ['key' => 'value1'], 'value2' => ['key' => 'value2'], ], + $rows, + [], + 'key', ], - 'null-key and composite.key' => [ - null, + [ [ - ['table.key' => 'value1'], - ['table.key' => 'value2'], + 'value1' => ['key' => 'value1'], + 'value2' => ['key' => 'value2'], ], + $rows, + [], + static fn ($row) => $row['key'], + ], + [ [ - ['table.key' => 'value1'], - ['table.key' => 'value2'], + ['key' => 'VALUE1'], + ['key' => 'VALUE2'], ], + $rows, + [], + null, + $resultCallback, ], - 'key with space' => [ - 'table key', + [ [ - ['table key' => 'value1'], - ['table key' => 'value2'], + 'value1' => ['value1' => ['key' => 'value1']], + 'value2' => ['value2' => ['key' => 'value2']], + ], + $rows, + ['key'], + 'key', + ], + [ + [ + 'value1' => ['value1' => ['key' => 'value1']], + 'value2' => ['value2' => ['key' => 'value2']], ], + $rows, + ['key'], + static fn ($row) => $row['key'], + ], + [ [ - 'value1' => ['table key' => 'value1'], - 'value2' => ['table key' => 'value2'], + 'value1' => [['key' => 'VALUE1']], + 'value2' => [['key' => 'VALUE2']], ], + $rows, + ['key'], + null, + $resultCallback, ], - 'composite-key and composite key' => [ - 'table.key', + [ [ - ['table.key' => 'value1'], - ['table.key' => 'value2'], + 'value1' => ['value1' => ['key' => 'VALUE1']], + 'value2' => ['value2' => ['key' => 'VALUE2']], ], + $rows, + ['key'], + 'key', + $resultCallback, + ], + [ [ - 'value1' => ['table.key' => 'value1'], - 'value2' => ['table.key' => 'value2'], + 'value1' => ['value1' => ['key' => 'VALUE1']], + 'value2' => ['value2' => ['key' => 'VALUE2']], ], + $rows, + ['key'], + static fn ($row) => $row['key'], + $resultCallback, ], ]; } From 511acbab55808f28f64c59c4f9da3141d08eea99 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 6 Apr 2025 08:40:24 +0000 Subject: [PATCH 2/6] Apply fixes from StyleCI --- tests/Provider/DbArrayHelperProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Provider/DbArrayHelperProvider.php b/tests/Provider/DbArrayHelperProvider.php index 5c92dbeb3..8ef30e779 100644 --- a/tests/Provider/DbArrayHelperProvider.php +++ b/tests/Provider/DbArrayHelperProvider.php @@ -6,6 +6,7 @@ use ArrayIterator; use Yiisoft\Db\Schema\Data\JsonLazyArray; + use function is_object; class DbArrayHelperProvider From 64c7f6593bc6a40b39d04f5ce7d41357fe65a278 Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 6 Apr 2025 16:13:19 +0700 Subject: [PATCH 3/6] Fix psalm --- src/Helper/DbArrayHelper.php | 13 ++++++++++--- src/Query/Query.php | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Helper/DbArrayHelper.php b/src/Helper/DbArrayHelper.php index b0a9dff2f..9c3ce5298 100644 --- a/src/Helper/DbArrayHelper.php +++ b/src/Helper/DbArrayHelper.php @@ -85,10 +85,10 @@ final class DbArrayHelper * @param Closure|null $resultCallback The callback function that will be called with the result array. This can be * used to modify the result before returning it. * - * @return array[] The arranged array. + * @return (array|object)[] The arranged array. * * @psalm-param IndexBy|null $indexBy - * @psalm-suppress MixedArrayAssignment + * @psalm-param ResultCallback|null $resultCallback */ public static function arrange( array $rows, @@ -119,11 +119,13 @@ public static function arrange( $lastArray = &$lastArray[$value]; } + /** @psalm-suppress MixedArrayAssignment */ $lastArray[] = $element; unset($lastArray); } + /** @var array[] $arranged */ if ($indexBy !== null || $resultCallback !== null) { self::indexArranged($arranged, $indexBy, $resultCallback, count($arrangeBy)); } @@ -161,7 +163,7 @@ public static function arrange( * @param Closure|null $resultCallback The callback function that will be called with the result array. This can be * used to modify the result before returning it. * - * @return array[] The indexed array. + * @return (array|object)[] The indexed array. * * @psalm-param IndexBy|null $indexBy * @psalm-param ResultCallback|null $resultCallback @@ -280,6 +282,10 @@ public static function toArray(array|object $object): array /** * Recursively indexes the arranged array. + * + * @psalm-assert (array|object)[] $arranged + * @psalm-param IndexBy|null $indexBy + * @psalm-param ResultCallback|null $resultCallback */ private static function indexArranged( array &$arranged, @@ -287,6 +293,7 @@ private static function indexArranged( Closure|null $resultCallback, int $depth, ): void { + /** @var array[] $rows */ foreach ($arranged as &$rows) { if ($depth > 1) { self::indexArranged($rows, $indexBy, $resultCallback, $depth - 1); diff --git a/src/Query/Query.php b/src/Query/Query.php index 7034832c5..079d96ce6 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -251,6 +251,7 @@ public function average(string $sql): int|float|null|string public function batch(int $batchSize = 100): BatchQueryResultInterface { + /** @psalm-suppress InvalidArgument */ return $this->db ->createBatchQueryResult($this) ->batchSize($batchSize) @@ -328,6 +329,7 @@ public function distinct(bool|null $value = true): static public function each(int $batchSize = 100): BatchQueryResultInterface { + /** @psalm-suppress InvalidArgument */ return $this->db ->createBatchQueryResult($this, true) ->batchSize($batchSize) From d96f44a83b51e7e8f58cbe9c6470fb3d67b093dd Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 6 Apr 2025 16:27:03 +0700 Subject: [PATCH 4/6] Improve test coverage --- src/Helper/DbArrayHelper.php | 5 +++++ tests/Provider/DbArrayHelperProvider.php | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Helper/DbArrayHelper.php b/src/Helper/DbArrayHelper.php index 9c3ce5298..39cfe6f75 100644 --- a/src/Helper/DbArrayHelper.php +++ b/src/Helper/DbArrayHelper.php @@ -34,6 +34,11 @@ final class DbArrayHelper * For example: * * ```php + * $array = [ + * ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + * ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + * ]; * $result = DbArrayHelper::arrange($rows, ['id']); * ``` * diff --git a/tests/Provider/DbArrayHelperProvider.php b/tests/Provider/DbArrayHelperProvider.php index 8ef30e779..86f01c325 100644 --- a/tests/Provider/DbArrayHelperProvider.php +++ b/tests/Provider/DbArrayHelperProvider.php @@ -244,6 +244,30 @@ public static function arrange() static fn ($row) => $row['key'], $resultCallback, ], + [ + [ + '123' => [ + 'laptop' => [ + 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ], + ], + '345' => [ + 'tablet' => [ + 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ], + 'smartphone' => [ + 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ], + ], + ], + [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ], + ['id', 'device'], + 'data', + ], ]; } From f681ed4a832322f0898a3236b3b02446f720563b Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 6 Apr 2025 23:03:53 +0700 Subject: [PATCH 5/6] Update CHANGELOG.md and UPGRADE.md --- CHANGELOG.md | 3 ++- UPGRADE.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef4796c52..82c7ee9a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,7 @@ - Enh #917: Rename `ColumnSchemaInterface` to `ColumnInterface` (@Tigrov) - Enh #919: Replace `name()` with immutable `withName()` method in `ColumnInterface` interface (@Tigrov) - Enh #921: Move `DataType` class to `Yiisoft\Db\Constant` namespace (@Tigrov) -- Enh #926: Refactor `DbArrayHelper` (@Tigrov) +- Enh #926, #954: Refactor `DbArrayHelper` (@Tigrov) - Enh #920: Move index constants to the appropriate DBMS driver's `IndexType` and `IndexMethod` classes (@Tigrov) - New #928: Add `ReferentialAction` class with constants of possible values of referential actions (@Tigrov) - Enh #929: Refactor array, structured and JSON column type expressions and expression builders (@Tigrov) @@ -75,6 +75,7 @@ - New #942: Allow PHP backed enums as values (@Tigrov) - Enh #943: Add `getCacheKey()` and `getCacheTag()` methods to `AbstractPdoSchema` class (@Tigrov) - Enh #925, #951: Add callback to `Query::all()` and `Query::one()` methods (@Tigrov, @vjik) +- New #954: Add `DbArrayHelper::arrange()` method (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index ac451e5fd..5650f78ae 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -131,6 +131,7 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - `SchemaInterface::hasTable()` - returns whether the specified table exists in database; - `SchemaInterface::hasSchema()` - returns whether the specified schema exists in database; - `SchemaInterface::hasView()` - returns whether the specified view exists in database; +- `DbArrayHelper::arrange()` - arranges an array by specified keys; ### Remove methods From 169f432be534b22854a610c77b3d04a6fd9637cc Mon Sep 17 00:00:00 2001 From: Tigrov Date: Sun, 6 Apr 2025 23:25:27 +0700 Subject: [PATCH 6/6] Fix psalm --- src/Helper/DbArrayHelper.php | 4 +++- src/Query/Query.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Helper/DbArrayHelper.php b/src/Helper/DbArrayHelper.php index 39cfe6f75..a9cbeb485 100644 --- a/src/Helper/DbArrayHelper.php +++ b/src/Helper/DbArrayHelper.php @@ -92,6 +92,7 @@ final class DbArrayHelper * * @return (array|object)[] The arranged array. * + * @psalm-param list $rows * @psalm-param IndexBy|null $indexBy * @psalm-param ResultCallback|null $resultCallback */ @@ -170,6 +171,7 @@ public static function arrange( * * @return (array|object)[] The indexed array. * + * @psalm-param list $rows * @psalm-param IndexBy|null $indexBy * @psalm-param ResultCallback|null $resultCallback */ @@ -298,7 +300,7 @@ private static function indexArranged( Closure|null $resultCallback, int $depth, ): void { - /** @var array[] $rows */ + /** @var list $rows */ foreach ($arranged as &$rows) { if ($depth > 1) { self::indexArranged($rows, $indexBy, $resultCallback, $depth - 1); diff --git a/src/Query/Query.php b/src/Query/Query.php index 079d96ce6..1283f964c 100644 --- a/src/Query/Query.php +++ b/src/Query/Query.php @@ -251,7 +251,7 @@ public function average(string $sql): int|float|null|string public function batch(int $batchSize = 100): BatchQueryResultInterface { - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidArgument, ArgumentTypeCoercion */ return $this->db ->createBatchQueryResult($this) ->batchSize($batchSize) @@ -329,7 +329,7 @@ public function distinct(bool|null $value = true): static public function each(int $batchSize = 100): BatchQueryResultInterface { - /** @psalm-suppress InvalidArgument */ + /** @psalm-suppress InvalidArgument, ArgumentTypeCoercion */ return $this->db ->createBatchQueryResult($this, true) ->batchSize($batchSize)