From 53757b7e5f71a04d10383b3ced0f4fed4eb02223 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 2 Sep 2026 12:50:10 +0200 Subject: [PATCH] fix(appconfig-userpreference): Fix doc types for returned arrays Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: Joas Schilling --- lib/private/AppConfig.php | 16 +++++--- lib/private/Config/UserConfig.php | 32 ++++++++------- lib/public/Config/IUserConfig.php | 16 +++++++- lib/public/IAppConfig.php | 8 +++- tests/lib/AppConfigIntegrationTest.php | 43 ++++++++++++++++++++ tests/lib/Config/UserConfigTest.php | 54 ++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 21 deletions(-) diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index 0887a41b2d0a8..98cbe4a01b9de 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -300,7 +300,13 @@ function (string $key) use ($prefix): bool { * @param bool $lazy search within lazy loaded config * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others) * - * @return array [appId => configValue] + * @return ( + * $typedAs is IAppConfig::VALUE_INT ? array : ( + * $typedAs is IAppConfig::VALUE_FLOAT ? array : ( + * $typedAs is IAppConfig::VALUE_BOOL ? array : ( + * $typedAs is IAppConfig::VALUE_ARRAY ? array : ( + * $typedAs is int ? array : + * array))))) [appId => configValue] * @since 29.0.0 */ #[\Override] @@ -1590,11 +1596,11 @@ private function convertTypedValue(string $value, int $type): string|int|float|b return in_array(strtolower($value), ['1', 'true', 'yes', 'on']); case self::VALUE_ARRAY: try { - return json_decode($value, true, flags: JSON_THROW_ON_ERROR); - } catch (JsonException $e) { - // ignoreable + $decoded = json_decode($value, true, flags: JSON_THROW_ON_ERROR); + } catch (JsonException) { + $decoded = null; } - break; + return is_array($decoded) ? $decoded : []; } return $value; } diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index 2869b590f5e0a..a4ebdf4d33b27 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -333,7 +333,13 @@ public function getAllValues(string $userId, bool $filtered = false): array { * @param bool $lazy search within lazy loaded config * @param ValueType|null $typedAs enforce type for the returned values * - * @return array [appId => value] + * @return ( + * $typedAs is ValueType::INT ? array : ( + * $typedAs is ValueType::FLOAT ? array : ( + * $typedAs is ValueType::BOOL ? array : ( + * $typedAs is ValueType::ARRAY ? array : ( + * $typedAs is ValueType ? array : + * array))))) [appId => value] * @since 31.0.0 */ #[\Override] @@ -354,11 +360,10 @@ public function getValuesByApps(string $userId, string $key, bool $lazy = false, $entry = $cache[$app][$key]; try { $value = $this->getDecryptedSensitiveValue($userId, $app, $key, $entry); - $value = $this->convertTypedValue($value, $typedAs ?? $entry->getType()); } catch (IncorrectTypeException|UnknownKeyException) { $value = $entry->getRawValue(); } - $values[$app] = $value; + $values[$app] = $this->convertTypedValue($value, $typedAs ?? $entry->getType()); } } @@ -373,7 +378,13 @@ public function getValuesByApps(string $userId, string $key, bool $lazy = false, * @param ValueType|null $typedAs enforce type for the returned values * @param array|null $userIds limit to a list of user ids * - * @return array [userId => value] + * @return ( + * $typedAs is ValueType::INT ? array : ( + * $typedAs is ValueType::FLOAT ? array : ( + * $typedAs is ValueType::BOOL ? array : ( + * $typedAs is ValueType::ARRAY ? array : ( + * $typedAs is ValueType ? array : + * array))))) [userId => value] * @since 31.0.0 */ #[\Override] @@ -397,12 +408,7 @@ public function getValuesByUsers( $executeAndStoreValue = function (IQueryBuilder $qb) use (&$values, $typedAs): IResult { $result = $qb->executeQuery(); while ($row = $result->fetchAssociative()) { - $value = $row['configvalue']; - try { - $value = $this->convertTypedValue($value, $typedAs ?? ValueType::from((int)$row['type'])); - } catch (IncorrectTypeException) { - } - $values[$row['userid']] = $value; + $values[$row['userid']] = $this->convertTypedValue($row['configvalue'], $typedAs ?? ValueType::from((int)$row['type'])); } return $result; }; @@ -1934,11 +1940,11 @@ private function convertTypedValue(string $value, ValueType $type): string|int|f return in_array(strtolower($value), ['1', 'true', 'yes', 'on']); case ValueType::ARRAY: try { - return json_decode($value, true, flags: JSON_THROW_ON_ERROR); + $decoded = json_decode($value, true, flags: JSON_THROW_ON_ERROR); } catch (JsonException) { - // ignoreable + $decoded = null; } - break; + return is_array($decoded) ? $decoded : []; } return $value; } diff --git a/lib/public/Config/IUserConfig.php b/lib/public/Config/IUserConfig.php index 7b2bd71e2e2c6..d68eb31bf6468 100644 --- a/lib/public/Config/IUserConfig.php +++ b/lib/public/Config/IUserConfig.php @@ -202,7 +202,13 @@ public function getAllValues(string $userId, bool $filtered = false): array; * @param bool $lazy search within lazy loaded config * @param ValueType|null $typedAs enforce type for the returned values * - * @return array [appId => value] + * @return ( + * $typedAs is ValueType::INT ? array : ( + * $typedAs is ValueType::FLOAT ? array : ( + * $typedAs is ValueType::BOOL ? array : ( + * $typedAs is ValueType::ARRAY ? array : ( + * $typedAs is ValueType ? array : + * array))))) [appId => value] * @throws \InvalidArgumentException if $userId or $key is invalid (too long, or empty string) * * @since 32.0.0 @@ -220,7 +226,13 @@ public function getValuesByApps(string $userId, string $key, bool $lazy = false, * @param ValueType|null $typedAs enforce type for the returned values * @param array|null $userIds limit the search to a list of user ids * - * @return array [userId => value] + * @return ( + * $typedAs is ValueType::INT ? array : ( + * $typedAs is ValueType::FLOAT ? array : ( + * $typedAs is ValueType::BOOL ? array : ( + * $typedAs is ValueType::ARRAY ? array : ( + * $typedAs is ValueType ? array : + * array))))) [userId => value] * @throws \InvalidArgumentException if $app or $key is invalid (too long, or empty string) * * @since 32.0.0 diff --git a/lib/public/IAppConfig.php b/lib/public/IAppConfig.php index 790ce30d70a2b..c543782265500 100644 --- a/lib/public/IAppConfig.php +++ b/lib/public/IAppConfig.php @@ -155,7 +155,13 @@ public function getAllValues(string $app, string $prefix = '', bool $filtered = * @param bool $lazy search within lazy loaded config * @param int|null $typedAs enforce type for the returned values {@see self::VALUE_STRING} and others * - * @return array [appId => configValue] + * @return ( + * $typedAs is self::VALUE_INT ? array : ( + * $typedAs is self::VALUE_FLOAT ? array : ( + * $typedAs is self::VALUE_BOOL ? array : ( + * $typedAs is self::VALUE_ARRAY ? array : ( + * $typedAs is int ? array : + * array))))) [appId => configValue] * @since 29.0.0 */ public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array; diff --git a/tests/lib/AppConfigIntegrationTest.php b/tests/lib/AppConfigIntegrationTest.php index 8a65f8fc418d9..2d47e21a76c8c 100644 --- a/tests/lib/AppConfigIntegrationTest.php +++ b/tests/lib/AppConfigIntegrationTest.php @@ -421,6 +421,49 @@ public function testSearchValues(): void { $this->assertEqualsCanonicalizing(['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'], $config->searchValues('enabled')); } + public static function providerSearchValuesTypedAs(): array { + return [ + [ + 'enabled', IAppConfig::VALUE_STRING, + ['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'] + ], + [ + 'enabled', IAppConfig::VALUE_MIXED, + ['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'] + ], + [ + 'enabled', IAppConfig::VALUE_BOOL, + ['testapp' => true, '123456' => true, 'anotherapp' => false] + ], + [ + 'enabled', IAppConfig::VALUE_INT, + ['testapp' => 0, '123456' => 0, 'anotherapp' => 0] + ], + [ + 'enabled', IAppConfig::VALUE_ARRAY, + ['testapp' => [], '123456' => [], 'anotherapp' => []] + ], + [ + 'array', IAppConfig::VALUE_ARRAY, + ['typed' => ['test' => 1]] + ], + [ + 'int', IAppConfig::VALUE_INT, + ['typed' => 42] + ], + [ + 'float', IAppConfig::VALUE_FLOAT, + ['typed' => 3.14] + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerSearchValuesTypedAs')] + public function testSearchValuesTypedAs(string $key, int $typedAs, array $result): void { + $config = $this->generateAppConfig(); + $this->assertEqualsCanonicalizing($result, $config->searchValues($key, false, $typedAs)); + } + public function testGetValueString(): void { $config = $this->generateAppConfig(); $this->assertSame('value', $config->getValueString('typed', 'string', '')); diff --git a/tests/lib/Config/UserConfigTest.php b/tests/lib/Config/UserConfigTest.php index aecd513b7d4ce..dede5c904ebea 100644 --- a/tests/lib/Config/UserConfigTest.php +++ b/tests/lib/Config/UserConfigTest.php @@ -690,6 +690,32 @@ public static function providerSearchValuesByApps(): array { 'app2' => 0, 'app3' => 0, ] + ], + [ + 'user1', 'key1', false, ValueType::BOOL, + [ + 'app1' => false, + 'app3' => false, + ] + ], + [ + 'user1', 'key1', false, ValueType::ARRAY, + [ + 'app1' => [], + 'app3' => [], + ] + ], + [ + 'user1', 'fast_array', false, ValueType::ARRAY, + [ + 'app1' => ['year' => 2024], + ] + ], + [ + 'user1', 'fast_array_sensitive', false, ValueType::ARRAY, + [ + 'app1' => ['password' => 'pwd'], + ] ] ]; } @@ -739,6 +765,34 @@ public static function providerSearchValuesByUsers(): array { 'user5' => 12, ] ], + [ + 'app3', 'key10', null, null, + [ + 'user1' => true, + 'user2' => false, + 'user4' => true, + ] + ], + [ + 'app2', 'key2', ValueType::BOOL, ['user1', 'user3'], + [ + 'user1' => false, + 'user3' => false, + ] + ], + [ + 'app2', 'key2', ValueType::ARRAY, ['user1', 'user3'], + [ + 'user1' => [], + 'user3' => [], + ] + ], + [ + 'app1', 'fast_array', ValueType::ARRAY, null, + [ + 'user1' => ['year' => 2024], + ] + ], ]; }