Skip to content

Commit cca201a

Browse files
authored
Merge pull request #140 from fschmtt/improve-map-type
feat: improve map type
2 parents 2e9fb00 + de23ad6 commit cca201a

File tree

7 files changed

+167
-43
lines changed

7 files changed

+167
-43
lines changed

src/Serializer/MapNormalizer.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@
66

77
use ArrayObject;
88
use Fschmtt\Keycloak\Type\Map;
9+
use InvalidArgumentException;
910
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
1011

1112
class MapNormalizer implements NormalizerInterface
1213
{
1314
/**
1415
* @param array<string, mixed> $context
1516
*/
16-
public function normalize(mixed $object, ?string $format = null, array $context = []): ArrayObject
17+
public function normalize(mixed $data, ?string $format = null, array $context = []): ArrayObject
1718
{
18-
return new ArrayObject($object);
19+
if (!$data instanceof Map) {
20+
throw new InvalidArgumentException(sprintf('Data must be an instance of "%s"', Map::class));
21+
}
22+
23+
return new ArrayObject($data->jsonSerialize());
1924
}
2025

2126
/**

src/Serializer/Serializer.php

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function __construct(
4444
new ArrayDenormalizer(),
4545
new CollectionDenormalizer($propertyNormalizer),
4646
new MapNormalizer(),
47+
new MapDenormalizer(),
4748
new AttributeNormalizer($propertyNormalizer, $keycloakVersion),
4849
$propertyNormalizer,
4950
], [

src/Type/Map.php

+45-6
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,21 @@
77
use ArrayIterator;
88
use Countable;
99
use IteratorAggregate;
10+
use OutOfBoundsException;
1011
use Traversable;
1112

12-
use function count;
13-
1413
/**
15-
* @internal
14+
* @template T
1615
*
17-
* @implements IteratorAggregate<string, mixed>
16+
* @implements IteratorAggregate<string, T>
1817
*/
1918
class Map extends Type implements Countable, IteratorAggregate
2019
{
2120
/**
22-
* @param array<mixed> $map
21+
* @param array<string, T> $map
2322
*/
2423
public function __construct(
25-
private readonly array $map = [],
24+
private array $map = [],
2625
) {}
2726

2827
public function jsonSerialize(): object
@@ -39,4 +38,44 @@ public function count(): int
3938
{
4039
return count($this->map);
4140
}
41+
42+
public function contains(string $key): bool
43+
{
44+
return array_key_exists($key, $this->map);
45+
}
46+
47+
public function get(string $key): mixed
48+
{
49+
if (!$this->contains($key)) {
50+
throw new OutOfBoundsException(sprintf('Key "%s" does not exist in map', $key));
51+
}
52+
53+
return $this->map[$key];
54+
}
55+
56+
public function with(string $key, mixed $value): self
57+
{
58+
$clone = clone $this;
59+
60+
$clone->map[$key] = $value;
61+
62+
return $clone;
63+
}
64+
65+
public function without(string $key): self
66+
{
67+
$clone = clone $this;
68+
69+
unset($clone->map[$key]);
70+
71+
return $clone;
72+
}
73+
74+
/**
75+
* @return array<string, T>
76+
*/
77+
public function getMap(): array
78+
{
79+
return $this->map;
80+
}
4281
}

tests/Integration/Resource/RealmsTest.php

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Fschmtt\Keycloak\Collection\RealmCollection;
88
use Fschmtt\Keycloak\Representation\Realm;
99
use Fschmtt\Keycloak\Test\Integration\IntegrationTestBehaviour;
10+
use Fschmtt\Keycloak\Type\Map;
1011
use PHPUnit\Framework\TestCase;
1112

1213
class RealmsTest extends TestCase
@@ -103,4 +104,21 @@ public function testCanDeleteAdminEvents(): void
103104

104105
$this->getKeycloak()->realms()->deleteAdminEvents('master');
105106
}
107+
108+
public function testCanUpdateRealmAttributes(): void
109+
{
110+
$realm = $this->getKeycloak()->realms()->get(realm: 'master');
111+
112+
static::assertFalse($realm->getAttributes()->contains('termsUrl'));
113+
114+
$realm = $realm->withAttributes(new Map([
115+
'termsUrl' => 'https://example.com/terms',
116+
]));
117+
118+
$this->getKeycloak()->realms()->update($realm->getRealm(), $realm);
119+
120+
$updatedRealm = $this->getKeycloak()->realms()->get(realm: $realm->getRealm());
121+
122+
static::assertEquals('https://example.com/terms', $updatedRealm->getAttributes()->get('termsUrl'));
123+
}
106124
}

tests/Unit/Representation/GroupTest.php

+13-13
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ class GroupTest extends TestCase
1919
protected function setUp(): void
2020
{
2121
$subGroup = new Group(
22-
access: new Map(['acl-a', 'acl-b']),
23-
attributes: new Map(['attr-1', 'attr-2']),
24-
clientRoles: new Map(['client-role-x', 'client-role-y', 'client-role-z']),
22+
access: new Map(['acl-a' => true, 'acl-b' => false]),
23+
attributes: new Map(['attr-1' => 'val-1', 'attr-2' => 'val-2']),
24+
clientRoles: new Map(['client-role-x' => ['foo', 'bar'], 'client-role-y' => ['foo', 'bar'], 'client-role-z' => ['foo', 'bar']]),
2525
id: 'unique-id',
2626
name: 'unique-name',
2727
path: '/where/am/i',
@@ -30,9 +30,9 @@ protected function setUp(): void
3030
$subGroup->withId('unique-id');
3131

3232
$this->group = new Group(
33-
access: new Map(['acl-a', 'acl-b']),
34-
attributes: new Map(['attr-1', 'attr-2']),
35-
clientRoles: new Map(['client-role-x', 'client-role-y', 'client-role-z']),
33+
access: new Map(['acl-a' => true, 'acl-b' => false]),
34+
attributes: new Map(['attr-1' => 'val-1', 'attr-2' => 'val-2']),
35+
clientRoles: new Map(['client-role-x' => ['foo', 'bar'], 'client-role-y' => ['foo', 'bar'], 'client-role-z' => ['foo', 'bar']]),
3636
id: 'unique-id',
3737
name: 'unique-name',
3838
path: '/where/am/i',
@@ -75,17 +75,17 @@ public static function provideProperties(): array
7575
{
7676
$group = [
7777
'access' => new Map([
78-
'acl-a',
79-
'acl-b',
78+
'acl-a' => true,
79+
'acl-b' => false,
8080
]),
8181
'attributes' => new Map([
82-
'attr-1',
83-
'attr-2',
82+
'attr-1' => 'val-1',
83+
'attr-2' => 'val-2',
8484
]),
8585
'clientRoles' => new Map([
86-
'client-role-x',
87-
'client-role-y',
88-
'client-role-z',
86+
'client-role-x' => ['foo', 'bar'],
87+
'client-role-y' => ['foo', 'bar'],
88+
'client-role-z' => ['foo', 'bar'],
8989
]),
9090
'id' => 'unique-id',
9191
'name' => 'unique-name',

tests/Unit/Serializer/MapNormalizerTest.php

+25-22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Fschmtt\Keycloak\Serializer\MapNormalizer;
99
use Fschmtt\Keycloak\Type\Map;
1010
use Generator;
11+
use InvalidArgumentException;
1112
use PHPUnit\Framework\Attributes\CoversClass;
1213
use PHPUnit\Framework\Attributes\DataProvider;
1314
use PHPUnit\Framework\TestCase;
@@ -17,59 +18,61 @@ class MapNormalizerTest extends TestCase
1718
{
1819
public function testSupportedTypes(): void
1920
{
20-
$denormalizer = new MapNormalizer();
21+
$normalizer = new MapNormalizer();
2122

2223
static::assertSame(
2324
[Map::class => true],
24-
$denormalizer->getSupportedTypes('json'),
25+
$normalizer->getSupportedTypes('json'),
2526
);
2627
}
2728

2829
public function testSupportsNormalization(): void
2930
{
30-
$denormalizer = new MapNormalizer();
31+
$normalizer = new MapNormalizer();
3132

32-
static::assertTrue($denormalizer->supportsNormalization(new Map()));
33-
static::assertFalse($denormalizer->supportsNormalization([]));
33+
static::assertTrue($normalizer->supportsNormalization(new Map()));
34+
static::assertFalse($normalizer->supportsNormalization([]));
3435
}
36+
3537
#[DataProvider('maps')]
3638
public function testNormalize(mixed $value, ArrayObject $expected): void
3739
{
38-
$denormalizer = new MapNormalizer();
40+
$normalizer = new MapNormalizer();
3941

40-
self::assertEquals(
42+
static::assertEquals(
4143
$expected,
42-
$denormalizer->normalize($value, Map::class),
44+
$normalizer->normalize($value, Map::class),
4345
);
4446
}
4547

48+
public function testThrowsIfDataIsNotAMap(): void
49+
{
50+
$normalizer = new MapNormalizer();
51+
52+
static::expectException(InvalidArgumentException::class);
53+
static::expectExceptionMessage(sprintf('Data must be an instance of "%s"', Map::class));
54+
55+
$normalizer->normalize([]);
56+
}
57+
4658
public static function maps(): Generator
4759
{
48-
yield 'filled array' => [
49-
[
60+
yield 'filled map' => [
61+
new Map([
5062
'a' => 1,
5163
'b' => 2,
5264
'c' => 3,
53-
],
65+
]),
5466
new ArrayObject([
5567
'a' => 1,
5668
'b' => 2,
5769
'c' => 3,
5870
]),
5971
];
6072

61-
yield 'empty array' => [
62-
[],
73+
yield 'empty map' => [
74+
new Map(),
6375
new ArrayObject(),
6476
];
65-
66-
yield Map::class => [
67-
new ArrayObject([
68-
'a' => 1,
69-
]),
70-
new ArrayObject([
71-
'a' => 1,
72-
]),
73-
];
7477
}
7578
}

tests/Unit/Type/MapTest.php

+58
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Fschmtt\Keycloak\Test\Unit\Type;
66

77
use Fschmtt\Keycloak\Type\Map;
8+
use OutOfBoundsException;
89
use PHPUnit\Framework\Attributes\CoversClass;
910
use PHPUnit\Framework\TestCase;
1011

@@ -61,4 +62,61 @@ public function testCanBeCounted(): void
6162

6263
static::assertCount(3, $map);
6364
}
65+
66+
public function testContains(): void
67+
{
68+
$map = new Map(['key-1' => 'value-1', 'key-2' => 'value-2']);
69+
70+
static::assertTrue($map->contains('key-1'));
71+
static::assertTrue($map->contains('key-2'));
72+
static::assertFalse($map->contains('key-3'));
73+
}
74+
75+
public function testGet(): void
76+
{
77+
$map = new Map(['key-1' => 'value-1', 'key-2' => 'value-2']);
78+
79+
static::assertSame('value-1', $map->get('key-1'));
80+
static::assertSame('value-2', $map->get('key-2'));
81+
}
82+
83+
public function testGetThrows(): void
84+
{
85+
$map = new Map(['key-1' => 'value-1', 'key-2' => 'value-2']);
86+
87+
static::expectException(OutOfBoundsException::class);
88+
static::expectExceptionMessage('Key "key-3" does not exist in map');
89+
90+
$map->get('key-3');
91+
}
92+
93+
public function testWith(): void
94+
{
95+
$map = new Map(['key-1' => 'value-1', 'key-2' => 'value-2']);
96+
97+
$updatedMap = $map->with('key-3', 'value-3');
98+
99+
static::assertNotSame($map, $updatedMap);
100+
static::assertCount(2, $map);
101+
static::assertCount(3, $updatedMap);
102+
}
103+
104+
public function testWithout(): void
105+
{
106+
$map = new Map(['key-1' => 'value-1', 'key-2' => 'value-2']);
107+
108+
$updatedMap = $map->without('key-2');
109+
110+
static::assertNotSame($map, $updatedMap);
111+
static::assertCount(2, $map);
112+
static::assertCount(1, $updatedMap);
113+
}
114+
115+
public function testGetMap(): void
116+
{
117+
$inner = ['key-1' => 'value-1', 'key-2' => 'value-2'];
118+
$map = new Map($inner);
119+
120+
static::assertSame($inner, $map->getMap());
121+
}
64122
}

0 commit comments

Comments
 (0)