Skip to content

Commit cab2853

Browse files
committed
feature: allow integer keys in iterables as per PSR integration tests
In the (non-official) PSR integration tests are checks for iterables with a non-empty-string cache key `0`. Internal PHP type juggling converts integerish strings to `int` which leads to a type conflict when passing these values to our internal assertion. Therefore, this patch provides compatibility for integer keys in methods consuming iterable keys either in key-value or key-only combination. Signed-off-by: Maximilian Bösing <[email protected]>
1 parent ba823c8 commit cab2853

File tree

4 files changed

+56
-30
lines changed

4 files changed

+56
-30
lines changed

src/Storage/Adapter/AbstractAdapter.php

+20-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use function array_values;
2525
use function func_num_args;
2626
use function is_array;
27+
use function is_int;
2728
use function is_string;
2829
use function preg_match;
2930
use function sprintf;
@@ -625,7 +626,7 @@ public function setItems(array $keyValuePairs): array
625626
}
626627

627628
Assert::isList($result);
628-
Assert::allStringNotEmpty($result);
629+
$this->assertValidKeys(array_keys($result));
629630
return $result;
630631
}
631632

@@ -849,7 +850,7 @@ public function replaceItems(array $keyValuePairs): array
849850
}
850851

851852
Assert::isList($result);
852-
Assert::allStringNotEmpty($result);
853+
$this->assertValidKeys(array_keys($result));
853854
return $result;
854855
}
855856

@@ -1013,12 +1014,12 @@ public function touchItems(array $keys): array
10131014

10141015
$result = $this->triggerPost(__FUNCTION__, $args, $result);
10151016
Assert::isList($result);
1016-
Assert::allStringNotEmpty($result);
1017+
self::assertValidKeys(array_keys($result));
10171018
return $result;
10181019
} catch (Throwable $throwable) {
10191020
$result = $this->triggerThrowable(__FUNCTION__, $args, $keys, $throwable);
10201021
Assert::isList($result);
1021-
Assert::allStringNotEmpty($result);
1022+
self::assertValidKeys(array_keys($result));
10221023
return $result;
10231024
}
10241025
}
@@ -1235,13 +1236,13 @@ protected function normalizeKeyValuePairs(array $keyValuePairs): void
12351236
}
12361237

12371238
/**
1238-
* @psalm-assert non-empty-string $key
1239+
* @psalm-assert non-empty-string|int $key
12391240
*/
12401241
protected function assertValidKey(mixed $key): void
12411242
{
1242-
if (! is_string($key)) {
1243+
if (! is_int($key) && ! is_string($key)) {
12431244
throw new Exception\InvalidArgumentException(
1244-
"Key has to be string"
1245+
"Key has to be either string or int"
12451246
);
12461247
}
12471248

@@ -1251,6 +1252,8 @@ protected function assertValidKey(mixed $key): void
12511252
);
12521253
}
12531254

1255+
$key = (string) $key;
1256+
12541257
$pattern = $this->getOptions()->getKeyPattern();
12551258
if ($pattern !== '' && ! preg_match($pattern, $key)) {
12561259
throw new Exception\InvalidArgumentException(
@@ -1282,4 +1285,14 @@ protected function assertValidKeyValuePairs(mixed $keyValuePairs): void
12821285
$this->assertValidKey($key);
12831286
}
12841287
}
1288+
1289+
/**
1290+
* @psalm-assert list<non-empty-string|int> $keys
1291+
*/
1292+
private function assertValidKeys(array $keys): void
1293+
{
1294+
foreach ($keys as $key) {
1295+
$this->assertValidKey($key);
1296+
}
1297+
}
12851298
}

src/Storage/StorageInterface.php

+21-22
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@
44

55
use Laminas\Cache\Exception\ExceptionInterface;
66

7+
/**
8+
* NOTE: when providing integrish cache keys in iterables, internal array conversion might convert these to int, even
9+
* tho they were non-empty-string beforehand. See https://3v4l.org/GsiBl for more details.
10+
*
11+
* @psalm-type CacheKeyInIterableType = non-empty-string|int
12+
*/
713
interface StorageInterface
814
{
9-
/**
10-
* Set options.
11-
*/
1215
public function setOptions(iterable|Adapter\AdapterOptions $options): self;
1316

14-
/**
15-
* Get options
16-
*/
1717
public function getOptions(): Adapter\AdapterOptions;
1818

1919
/* reading */
2020
/**
21-
* Get an item.
22-
*
2321
* @param non-empty-string $key
2422
* @param-out bool $success
2523
* @return mixed Data on success, null on failure
@@ -30,8 +28,8 @@ public function getItem(string $key, bool|null &$success = null, mixed &$casToke
3028
/**
3129
* Get multiple items.
3230
*
33-
* @param non-empty-list<non-empty-string> $keys
34-
* @return array<non-empty-string,mixed> Associative array of keys and values
31+
* @param non-empty-list<CacheKeyInIterableType> $keys
32+
* @return array<CacheKeyInIterableType,mixed> Associative array of keys and values
3533
* @throws ExceptionInterface
3634
*/
3735
public function getItems(array $keys): array;
@@ -47,8 +45,8 @@ public function hasItem(string $key): bool;
4745
/**
4846
* Test multiple items.
4947
*
50-
* @param non-empty-list<non-empty-string> $keys
51-
* @return list<non-empty-string> Array of found keys
48+
* @param non-empty-list<CacheKeyInIterableType> $keys
49+
* @return list<CacheKeyInIterableType> Array of found keys
5250
* @throws ExceptionInterface
5351
*/
5452
public function hasItems(array $keys): array;
@@ -65,24 +63,25 @@ public function setItem(string $key, mixed $value): bool;
6563
/**
6664
* Store multiple items.
6765
*
68-
* @param non-empty-array<non-empty-string,mixed> $keyValuePairs
69-
* @return list<non-empty-string> Array of not stored keys
66+
* @param non-empty-array<CacheKeyInIterableType,mixed> $keyValuePairs
67+
* @return list<CacheKeyInIterableType> Array of not stored keys
7068
* @throws ExceptionInterface
7169
*/
7270
public function setItems(array $keyValuePairs): array;
7371

7472
/**
7573
* Add an item.
7674
*
75+
* @param non-empty-string $key
7776
* @throws ExceptionInterface
7877
*/
7978
public function addItem(string $key, mixed $value): bool;
8079

8180
/**
8281
* Add multiple items.
8382
*
84-
* @param non-empty-array<non-empty-string,mixed> $keyValuePairs
85-
* @return list<non-empty-string> Array of not stored keys
83+
* @param non-empty-array<CacheKeyInIterableType,mixed> $keyValuePairs
84+
* @return list<CacheKeyInIterableType> Array of not stored keys
8685
* @throws ExceptionInterface
8786
*/
8887
public function addItems(array $keyValuePairs): array;
@@ -98,8 +97,8 @@ public function replaceItem(string $key, mixed $value): bool;
9897
/**
9998
* Replace multiple existing items.
10099
*
101-
* @param non-empty-array<non-empty-string,mixed> $keyValuePairs
102-
* @return list<non-empty-string> Array of not stored keys
100+
* @param non-empty-array<CacheKeyInIterableType,mixed> $keyValuePairs
101+
* @return list<CacheKeyInIterableType> Array of not stored keys
103102
* @throws ExceptionInterface
104103
*/
105104
public function replaceItems(array $keyValuePairs): array;
@@ -130,8 +129,8 @@ public function touchItem(string $key): bool;
130129
/**
131130
* Reset lifetime of multiple items.
132131
*
133-
* @param non-empty-list<non-empty-string> $keys
134-
* @return list<non-empty-string> Array of not updated keys
132+
* @param non-empty-list<CacheKeyInIterableType> $keys
133+
* @return list<CacheKeyInIterableType> Array of not updated keys
135134
* @throws ExceptionInterface
136135
*/
137136
public function touchItems(array $keys): array;
@@ -147,8 +146,8 @@ public function removeItem(string $key): bool;
147146
/**
148147
* Remove multiple items.
149148
*
150-
* @param non-empty-list<non-empty-string> $keys
151-
* @return list<non-empty-string> Array of not removed keys
149+
* @param non-empty-list<CacheKeyInIterableType> $keys
150+
* @return list<CacheKeyInIterableType> Array of not removed keys
152151
* @throws ExceptionInterface
153152
*/
154153
public function removeItems(array $keys): array;

test/Storage/Adapter/AbstractAdapterTest.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use ArrayObject;
88
use Laminas\Cache;
99
use Laminas\Cache\Exception;
10-
use Laminas\Cache\Exception\InvalidArgumentException;
1110
use Laminas\Cache\Exception\RuntimeException;
1211
use Laminas\Cache\Storage\Adapter\AbstractAdapter;
1312
use Laminas\Cache\Storage\Adapter\AdapterOptions;
@@ -19,6 +18,7 @@
1918
use Laminas\EventManager\ResponseCollection;
2019
use Laminas\Serializer\AdapterPluginManager;
2120
use Laminas\ServiceManager\ServiceManager;
21+
use LaminasTest\Cache\Storage\TestAsset\MockAdapter;
2222
use LaminasTest\Cache\Storage\TestAsset\MockPlugin;
2323
use PHPUnit\Framework\MockObject\MockObject;
2424
use PHPUnit\Framework\TestCase;
@@ -801,4 +801,17 @@ public function testCanCompareOldValueWithTokenWhenUsedWithSerializerPlugin(): v
801801

802802
self::assertTrue($storage->checkAndSetItem('bar', 'foo', 'baz'));
803803
}
804+
805+
public function testCanHandleIntegerishKeysInIterables(): void
806+
{
807+
$storage = new MockAdapter();
808+
$keyValuePair = ['0' => '0'];
809+
self::assertSame([], $storage->setItems($keyValuePair));
810+
self::assertSame([], $storage->addItems($keyValuePair));
811+
self::assertSame([0], $storage->replaceItems($keyValuePair));
812+
self::assertSame([], $storage->removeItems(array_keys($keyValuePair)));
813+
self::assertSame([], $storage->getItems(array_keys($keyValuePair)));
814+
self::assertSame([], $storage->hasItems(array_keys($keyValuePair)));
815+
self::assertSame([0], $storage->touchItems(array_keys($keyValuePair)));
816+
}
804817
}

test/Storage/TestAsset/MockAdapter.php

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class MockAdapter extends AbstractAdapter
1515
{
1616
protected function internalGetItem(string $normalizedKey, ?bool &$success = null, mixed &$casToken = null): mixed
1717
{
18+
$success = false;
1819
return null;
1920
}
2021

0 commit comments

Comments
 (0)