Skip to content

Commit 8d9d199

Browse files
authored
Merge pull request #4 from cspray/feature/config-refactor
Allow for many parameter stores to be created
2 parents e618977 + 5f72839 commit 8d9d199

12 files changed

+202
-131
lines changed

known-issues.xml

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<files psalm-version="5.12.0@f90118cdeacd0088e7215e64c0c99ceca819e176">
3+
<file src="src/ConfigParameterStoreFactory.php">
4+
<ArgumentTypeCoercion>
5+
<code>$identifier</code>
6+
<code>$identifier</code>
7+
</ArgumentTypeCoercion>
8+
</file>
39
<file src="src/PhpIncludeValueProvider.php">
410
<MixedArgumentTypeCoercion>
511
<code>$data</code>

src/SecretsParameterStore.php src/ConfigParameterStore.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use Cspray\Typiphy\TypeIntersect;
99
use Cspray\Typiphy\TypeUnion;
1010

11-
final class SecretsParameterStore implements ParameterStore {
11+
final class ConfigParameterStore implements ParameterStore {
1212

1313
/**
1414
* @var array<non-empty-string, Source>
@@ -20,7 +20,7 @@ final class SecretsParameterStore implements ParameterStore {
2020
* @param non-empty-string $storeNameDelimiter
2121
*/
2222
public function __construct(
23-
private readonly string $name = 'secrets',
23+
private readonly string $name,
2424
private readonly string $storeNameDelimiter = '.'
2525
) {}
2626

src/ConfigParameterStoreFactory.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Cspray\AnnotatedContainer\Secrets;
4+
5+
use Cspray\AnnotatedContainer\Bootstrap\ParameterStoreFactory;
6+
use Cspray\AnnotatedContainer\ContainerFactory\ParameterStore;
7+
use Cspray\AnnotatedContainer\Secrets\Exception\NoSourcesProvided;
8+
9+
final class ConfigParameterStoreFactory implements ParameterStoreFactory {
10+
11+
public function __construct(
12+
private readonly IdentifierSourceMap $sourceMap
13+
) {}
14+
15+
/**
16+
* @param string $identifier
17+
* @return ParameterStore
18+
* @throws NoSourcesProvided
19+
*/
20+
public function createParameterStore(string $identifier) : ParameterStore {
21+
$store = new ConfigParameterStore($identifier);
22+
$sources = $this->sourceMap->getSourcesForIdentifier($identifier);
23+
if ($sources === []) {
24+
throw NoSourcesProvided::fromNoSources($identifier);
25+
}
26+
foreach ($sources as $source) {
27+
$store->addSource($source);
28+
}
29+
30+
return $store;
31+
}
32+
33+
}

src/Exception/InvalidParameterStoreIdentifier.php

-16
This file was deleted.

src/Exception/InvalidSecretsKey.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
namespace Cspray\AnnotatedContainer\Secrets\Exception;
44

55
use Cspray\AnnotatedContainer\Exception\Exception;
6-
use Cspray\AnnotatedContainer\Secrets\SecretsParameterStore;
6+
use Cspray\AnnotatedContainer\Secrets\ConfigParameterStore;
77

88
final class InvalidSecretsKey extends Exception {
99

1010
public static function fromKeyInvalidSourceDelimiter(string $key, string $storeDelimiter) : self {
1111
return new self(sprintf(
1212
'The key "%s" passed to %s MUST contain at least one "%s" delimiter.',
1313
$key,
14-
SecretsParameterStore::class,
14+
ConfigParameterStore::class,
1515
$storeDelimiter
1616
));
1717
}
@@ -21,7 +21,7 @@ public static function fromMissingSource(string $key, string $source) : self {
2121
'The key "%s" specifies a secrets source "%s" that has not been added to %s',
2222
$key,
2323
$source,
24-
SecretsParameterStore::class
24+
ConfigParameterStore::class
2525
));
2626
}
2727

src/Exception/NoSourcesProvided.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
final class NoSourcesProvided extends Exception {
88

9-
public static function fromNoSources() : self {
10-
return new self('At least 1 secrets source MUST be provided to this ParameterStore factory.');
9+
public static function fromNoSources(string $identifier) : self {
10+
return new self(sprintf(
11+
'No configuration sources were found for the parameter store identifier "%s".',
12+
$identifier
13+
));
1114
}
1215

1316
}

src/IdentifierSourceMap.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Cspray\AnnotatedContainer\Secrets;
4+
5+
final class IdentifierSourceMap {
6+
7+
/**
8+
* @var array<non-empty-string, list<Source>>
9+
*/
10+
private array $map = [];
11+
12+
public function __construct() {}
13+
14+
/**
15+
* @param non-empty-string $identifier
16+
* @param list<Source> $sources
17+
*/
18+
public function withIdentifierAndSources(string $identifier, array $sources) : self {
19+
$new = clone $this;
20+
$new->map[$identifier] = $sources;
21+
return $new;
22+
}
23+
24+
/**
25+
* @param non-empty-string $identifier
26+
* @return list<Source>
27+
*/
28+
public function getSourcesForIdentifier(string $identifier) : array {
29+
return $this->map[$identifier] ?? [];
30+
}
31+
32+
33+
}

src/SecretsParameterStoreFactory.php

-35
This file was deleted.
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Cspray\AnnotatedContainer\Secrets\Test;
4+
5+
use Cspray\AnnotatedContainer\Secrets\ArrayValueProvider;
6+
use Cspray\AnnotatedContainer\Secrets\Exception\NoSourcesProvided;
7+
use Cspray\AnnotatedContainer\Secrets\ConfigParameterStore;
8+
use Cspray\AnnotatedContainer\Secrets\ConfigParameterStoreFactory;
9+
use Cspray\AnnotatedContainer\Secrets\IdentifierSourceMap;
10+
use Cspray\AnnotatedContainer\Secrets\SingleValueProviderSource;
11+
use Cspray\AnnotatedContainer\Secrets\Source;
12+
use PHPUnit\Framework\Attributes\CoversClass;
13+
use PHPUnit\Framework\TestCase;
14+
use function Cspray\Typiphy\stringType;
15+
16+
#[
17+
CoversClass(ConfigParameterStoreFactory::class),
18+
CoversClass(ConfigParameterStore::class),
19+
CoversClass(NoSourcesProvided::class),
20+
CoversClass(ArrayValueProvider::class),
21+
CoversClass(SingleValueProviderSource::class),
22+
CoversClass(IdentifierSourceMap::class)
23+
]
24+
final class ConfigParameterStoreFactoryTest extends TestCase {
25+
26+
public function testFactoryCreatesInstancesOfSecretsParameterStore() : void {
27+
$subject = new ConfigParameterStoreFactory(
28+
(new IdentifierSourceMap())
29+
->withIdentifierAndSources(
30+
'config',
31+
[$this->getMockBuilder(Source::class)->getMock()]
32+
)
33+
);
34+
35+
self::assertInstanceOf(
36+
ConfigParameterStore::class,
37+
$subject->createParameterStore('config')
38+
);
39+
}
40+
41+
public function testFactoryGivenNonSecretsParameterStoreIdentifierThrowsException() : void {
42+
$subject = new ConfigParameterStoreFactory(new IdentifierSourceMap());
43+
$this->expectException(NoSourcesProvided::class);
44+
$this->expectExceptionMessage('No configuration sources were found for the parameter store identifier "NotSecrets".');
45+
46+
$subject->createParameterStore('NotSecrets');
47+
}
48+
49+
public function testSourcesAreProvidedToSecretsParameterStore() : void {
50+
$subject = new ConfigParameterStoreFactory(
51+
(new IdentifierSourceMap())
52+
->withIdentifierAndSources(
53+
'secrets',
54+
[new SingleValueProviderSource('source', new ArrayValueProvider(['foo' => 'bar']))]
55+
)
56+
);
57+
$store = $subject->createParameterStore('secrets');
58+
self::assertInstanceOf(
59+
ConfigParameterStore::class,
60+
$store
61+
);
62+
self::assertSame('bar', $store->fetch(stringType(), 'source.foo'));
63+
}
64+
65+
public function testConfigParameterStoreNameIsIdentifierPassedIn() : void {
66+
$subject = new ConfigParameterStoreFactory(
67+
(new IdentifierSourceMap())
68+
->withIdentifierAndSources(
69+
'secrets',
70+
[new SingleValueProviderSource('source', new ArrayValueProvider(['foo' => 'bar']))]
71+
)
72+
);
73+
$store = $subject->createParameterStore('secrets');
74+
75+
self::assertSame('secrets', $store->getName());
76+
}
77+
78+
}

tests/SecretsParameterStoreTest.php tests/ConfigParameterStoreTest.php

+9-9
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33
namespace Cspray\AnnotatedContainer\Secrets\Test;
44

55
use Cspray\AnnotatedContainer\Secrets\Exception\InvalidSecretsKey;
6-
use Cspray\AnnotatedContainer\Secrets\SecretsParameterStore;
6+
use Cspray\AnnotatedContainer\Secrets\ConfigParameterStore;
77
use Cspray\AnnotatedContainer\Secrets\Source;
88
use PHPUnit\Framework\Attributes\CoversClass;
99
use PHPUnit\Framework\Attributes\DataProvider;
1010
use PHPUnit\Framework\TestCase;
1111
use function Cspray\Typiphy\stringType;
1212

1313
#[
14-
CoversClass(SecretsParameterStore::class),
14+
CoversClass(ConfigParameterStore::class),
1515
CoversClass(InvalidSecretsKey::class)
1616
]
17-
final class SecretsParameterStoreTest extends TestCase {
17+
final class ConfigParameterStoreTest extends TestCase {
1818

1919
public function testGetNameReturnsValuePassedToConstructor() : void {
20-
$subject = new SecretsParameterStore();
20+
$subject = new ConfigParameterStore('secrets');
2121

2222
self::assertSame('secrets', $subject->getName());
2323
}
@@ -32,31 +32,31 @@ public static function storeDelimiterProvider() : array {
3232

3333
#[DataProvider('storeDelimiterProvider')]
3434
public function testFetchValueWithoutSourceDelimiterThrowsException(string $storeDelimiter) : void {
35-
$subject = new SecretsParameterStore(storeNameDelimiter: $storeDelimiter);
35+
$subject = new ConfigParameterStore('secrets', storeNameDelimiter: $storeDelimiter);
3636

3737
self::expectException(InvalidSecretsKey::class);
3838
self::expectExceptionMessage(
39-
'The key "foo" passed to ' . SecretsParameterStore::class . ' MUST contain at least one "' . $storeDelimiter . '" delimiter.'
39+
'The key "foo" passed to ' . ConfigParameterStore::class . ' MUST contain at least one "' . $storeDelimiter . '" delimiter.'
4040
);
4141

4242
$subject->fetch(stringType(), 'foo');
4343
}
4444

4545
#[DataProvider('storeDelimiterProvider')]
4646
public function testFetchValueWithDelimiterAndNoSourcePresentThrowsException(string $storeDelimiter) : void {
47-
$subject = new SecretsParameterStore(storeNameDelimiter: $storeDelimiter);
47+
$subject = new ConfigParameterStore('secrets', storeNameDelimiter: $storeDelimiter);
4848

4949
self::expectException(InvalidSecretsKey::class);
5050
self::expectExceptionMessage(sprintf(
51-
'The key "foo%sbar" specifies a secrets source "foo" that has not been added to ' . SecretsParameterStore::class, $storeDelimiter
51+
'The key "foo%sbar" specifies a secrets source "foo" that has not been added to ' . ConfigParameterStore::class, $storeDelimiter
5252
));
5353

5454
$subject->fetch(stringType(), sprintf('foo%sbar', $storeDelimiter));
5555
}
5656

5757
#[DataProvider('storeDelimiterProvider')]
5858
public function testFetchValueWithDelimiterAndSourcePresentReturnsValueFromNamedSource(string $storeDelimiter) : void {
59-
$subject = new SecretsParameterStore(storeNameDelimiter: $storeDelimiter);
59+
$subject = new ConfigParameterStore('secrets', storeNameDelimiter: $storeDelimiter);
6060

6161
$source = $this->getMockBuilder(Source::class)->getMock();
6262
$source->expects($this->once())

tests/IdentifierSourceMapTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Cspray\AnnotatedContainer\Secrets\Test;
4+
5+
use Cspray\AnnotatedContainer\Secrets\IdentifierSourceMap;
6+
use Cspray\AnnotatedContainer\Secrets\Source;
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\TestCase;
9+
10+
#[CoversClass(IdentifierSourceMap::class)]
11+
final class IdentifierSourceMapTest extends TestCase {
12+
13+
public function testWithIdentifierAndSourcesReturnsNewInstance() : void {
14+
$a = new IdentifierSourceMap();
15+
$b = $a->withIdentifierAndSources('source', [$this->getMockBuilder(Source::class)->getMock()]);
16+
17+
self::assertNotSame($a, $b);
18+
}
19+
20+
public function testGetSourcesForIdentifierNotPresentReturnsEmptyArray() : void {
21+
$subject = new IdentifierSourceMap();
22+
23+
self::assertSame([], $subject->getSourcesForIdentifier('not present'));
24+
}
25+
26+
public function testGetSourcesWithIdentifierAndSourcesAdded() : void {
27+
$subject = new IdentifierSourceMap();
28+
$subject = $subject->withIdentifierAndSources('config store', $expected = [$this->getMockBuilder(Source::class)->getMock()]);
29+
30+
self::assertSame($expected, $subject->getSourcesForIdentifier('config store'));
31+
}
32+
33+
}

0 commit comments

Comments
 (0)