Skip to content

Commit de23ad6

Browse files
committed
feat: add map modifiers
1 parent 9a7b216 commit de23ad6

File tree

6 files changed

+139
-28
lines changed

6 files changed

+139
-28
lines changed

src/Serializer/MapNormalizer.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,18 +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-
assert($object instanceof Map);
19+
if (!$data instanceof Map) {
20+
throw new InvalidArgumentException(sprintf('Data must be an instance of "%s"', Map::class));
21+
}
1922

20-
return new ArrayObject($object->map);
23+
return new ArrayObject($data->jsonSerialize());
2124
}
2225

2326
/**

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-
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function testCanUpdateRealmAttributes(): void
109109
{
110110
$realm = $this->getKeycloak()->realms()->get(realm: 'master');
111111

112-
static::assertNull($realm->getAttributes()->map['termsUrl']);
112+
static::assertFalse($realm->getAttributes()->contains('termsUrl'));
113113

114114
$realm = $realm->withAttributes(new Map([
115115
'termsUrl' => 'https://example.com/terms',
@@ -119,6 +119,6 @@ public function testCanUpdateRealmAttributes(): void
119119

120120
$updatedRealm = $this->getKeycloak()->realms()->get(realm: $realm->getRealm());
121121

122-
static::assertEquals('https://example.com/terms', $updatedRealm->getAttributes()->map['termsUrl']);
122+
static::assertEquals('https://example.com/terms', $updatedRealm->getAttributes()->get('termsUrl'));
123123
}
124124
}

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

+15-4
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;
@@ -38,15 +39,25 @@ public function testNormalize(mixed $value, ArrayObject $expected): void
3839
{
3940
$normalizer = new MapNormalizer();
4041

41-
self::assertEquals(
42+
static::assertEquals(
4243
$expected,
4344
$normalizer->normalize($value, Map::class),
4445
);
4546
}
4647

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+
4758
public static function maps(): Generator
4859
{
49-
yield 'filled array' => [
60+
yield 'filled map' => [
5061
new Map([
5162
'a' => 1,
5263
'b' => 2,
@@ -59,8 +70,8 @@ public static function maps(): Generator
5970
]),
6071
];
6172

62-
yield 'empty array' => [
63-
new Map([]),
73+
yield 'empty map' => [
74+
new Map(),
6475
new ArrayObject(),
6576
];
6677
}

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)