Skip to content

Commit b773db9

Browse files
authored
Ensure array of enums can be injected (#174)
* Ensure array of enums can be injected * Reference entire version * Handle associative arrays correctly
1 parent aedb590 commit b773db9

12 files changed

+231
-21
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased Changes
8+
## [v1.3.0](https://github.com/cspray/annotated-container/tree/v1.3.0) - 2022-08-06
99

1010
### Added
1111

@@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
- Updated the parsing of the #[ServiceDelegate] attribute to implicitly determine the service to create off of the method return type if no argument is passed to the Attribute.
1717

18+
### Fixed
19+
20+
- Fixed an error where building a container from a cached definition was not respecting an enum or an array of enums as a value for `#[Inject]`.
21+
1822
## [v.1.2.1](https://github.com/cspray/annotated-container/tree/v1.2.1) - 2022-08-01
1923

2024
### Fixed

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
dev-main
1+
1.3.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainerFixture\ConfigurationWithArrayEnum;
4+
5+
enum FooEnum {
6+
case Bar;
7+
case Baz;
8+
case Qux;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainerFixture\ConfigurationWithArrayEnum;
4+
5+
use Cspray\AnnotatedContainer\Attribute\Configuration;
6+
use Cspray\AnnotatedContainer\Attribute\Inject;
7+
8+
#[Configuration]
9+
class MyConfiguration {
10+
11+
#[Inject([FooEnum::Bar, FooEnum::Qux])]
12+
public readonly array $cases;
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainerFixture;
4+
5+
use Cspray\Typiphy\ObjectType;
6+
use function Cspray\Typiphy\objectType;
7+
8+
class ConfigurationWithArrayEnumFixture implements Fixture {
9+
10+
public function getPath() : string {
11+
return __DIR__ . '/ConfigurationWithArrayEnum';
12+
}
13+
14+
public function myConfiguration() : ObjectType {
15+
return objectType(ConfigurationWithArrayEnum\MyConfiguration::class);
16+
}
17+
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainerFixture\ConfigurationWithAssocArrayEnum;
4+
5+
use Cspray\AnnotatedContainer\Attribute\Configuration;
6+
use Cspray\AnnotatedContainer\Attribute\Inject;
7+
8+
#[Configuration]
9+
class MyConfiguration {
10+
11+
#[Inject(['b' => MyEnum::B, 'c' => MyEnum::C])]
12+
public readonly array $letters;
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainerFixture\ConfigurationWithAssocArrayEnum;
4+
5+
enum MyEnum {
6+
case A;
7+
case B;
8+
case C;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainerFixture;
4+
5+
use Cspray\Typiphy\ObjectType;
6+
use function Cspray\Typiphy\objectType;
7+
8+
class ConfigurationWithAssocArrayEnumFixture implements Fixture {
9+
10+
public function getPath() : string {
11+
return __DIR__ . '/ConfigurationWithAssocArrayEnum';
12+
}
13+
14+
public function myConfiguration() : ObjectType {
15+
return objectType(ConfigurationWithAssocArrayEnum\MyConfiguration::class);
16+
}
17+
}

fixture_src/Fixtures.php

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Cspray\AnnotatedContainerFixture;
44

5+
use Cspray\AnnotatedContainer\Attribute\Configuration;
6+
57
final class Fixtures {
68

79
private function __construct() {}
@@ -142,4 +144,12 @@ public static function configurationWithEnum() : ConfigurationWithEnumFixture {
142144
return new ConfigurationWithEnumFixture();
143145
}
144146

147+
public static function configurationWithArrayEnum() : ConfigurationWithArrayEnumFixture {
148+
return new ConfigurationWithArrayEnumFixture();
149+
}
150+
151+
public static function configurationWithAssocArrayEnum() : ConfigurationWithAssocArrayEnumFixture {
152+
return new ConfigurationWithAssocArrayEnumFixture();
153+
}
154+
145155
}

src/Internal/Objects.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Cspray\AnnotatedContainer\Internal;
4+
5+
use Cspray\Typiphy\ObjectType;
6+
use UnitEnum;
7+
8+
/**
9+
* @Internal
10+
*/
11+
final class Objects {
12+
13+
private function __construct() {}
14+
15+
/**
16+
* @param class-string|ObjectType $type
17+
* @return bool
18+
*/
19+
public static function isEnum(string|ObjectType $type) : bool {
20+
$type = is_string($type) ? $type : $type->getName();
21+
return is_a($type, UnitEnum::class, true);
22+
}
23+
24+
}

src/JsonContainerDefinitionSerializer.php

+40-11
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
namespace Cspray\AnnotatedContainer;
44

5+
use Cspray\AnnotatedContainer\Internal\Objects;
56
use Cspray\Typiphy\Internal\NamedType;
67
use Cspray\Typiphy\ObjectType;
78
use Cspray\Typiphy\Type;
89
use Cspray\Typiphy\TypeIntersect;
910
use Cspray\Typiphy\TypeUnion;
11+
use ReflectionEnum;
1012
use UnitEnum;
1113
use function Cspray\Typiphy\arrayType;
1214
use function Cspray\Typiphy\boolType;
@@ -92,19 +94,34 @@ public function serialize(ContainerDefinition $containerDefinition) : string {
9294
];
9395
}
9496

97+
$parseValue = function(mixed $value) use(&$parseValue) : mixed {
98+
if (is_object($value) && Objects::isEnum($value::class)) {
99+
$parsedValue = $value->name;
100+
} else if (is_array($value)) {
101+
$parsedValue = [];
102+
foreach ($value as $key => $val) {
103+
$rawType = is_object($val) ? $val::class : gettype($val);
104+
$parsedValue[$key] = [
105+
'type' => self::convertStringToType($rawType)->getName(),
106+
'value' => $parseValue($val)
107+
];
108+
}
109+
} else {
110+
$parsedValue = $value;
111+
}
112+
113+
return $parsedValue;
114+
};
115+
95116
$injectDefinitions = [];
96117
foreach ($containerDefinition->getInjectDefinitions() as $injectDefinition) {
97-
$value = $injectDefinition->getValue();
98-
if (is_object($value) && (new \ReflectionClass($value))->isEnum()) {
99-
$value = $value->name;
100-
}
101118

102119
$injectDefinitions[] = [
103120
'injectTargetType' => $injectDefinition->getTargetIdentifier()->getClass()->getName(),
104121
'injectTargetMethod' => $injectDefinition->getTargetIdentifier()->getMethodName(),
105122
'injectTargetName' => $injectDefinition->getTargetIdentifier()->getName(),
106123
'type' => $injectDefinition->getType()->getName(),
107-
'value' => $value,
124+
'value' => $parseValue($injectDefinition->getValue()),
108125
'profiles' => $injectDefinition->getProfiles(),
109126
'storeName' => $injectDefinition->getStoreName()
110127
];
@@ -179,6 +196,23 @@ public function deserialize(string $serializedDefinition) : ContainerDefinition
179196
);
180197
}
181198

199+
$parseValue = function(Type|TypeUnion|TypeIntersect $type, mixed $value) use(&$parseValue) : mixed {
200+
if ($type instanceof ObjectType && Objects::isEnum($type)) {
201+
$enumReflection = new ReflectionEnum($type->getName());
202+
$parsedValue = $enumReflection->getCase($value)->getValue();
203+
} else if (is_array($value)) {
204+
$parsedValue = [];
205+
foreach ($value as $key => $val) {
206+
$type = self::convertStringToType($val['type']);
207+
$parsedValue[$key] = $parseValue($type, $val['value']);
208+
}
209+
} else {
210+
$parsedValue = $value;
211+
}
212+
213+
return $parsedValue;
214+
};
215+
182216
foreach ($data['injectDefinitions'] as $injectDefinition) {
183217
$injectBuilder = InjectDefinitionBuilder::forService(objectType($injectDefinition['injectTargetType']));
184218

@@ -197,13 +231,8 @@ public function deserialize(string $serializedDefinition) : ContainerDefinition
197231
);
198232
}
199233

200-
$value = $injectDefinition['value'];
201-
if ($type instanceof ObjectType && is_a($type->getName(), UnitEnum::class, true)) {
202-
$enumReflection = new \ReflectionEnum($type->getName());
203-
$value = $enumReflection->getCase($value)->getValue();
204-
}
205234

206-
$injectBuilder = $injectBuilder->withValue($value)
235+
$injectBuilder = $injectBuilder->withValue($parseValue($type, $injectDefinition['value']))
207236
->withProfiles(...$injectDefinition['profiles']);
208237

209238
if (!is_null($injectDefinition['storeName'])) {

test/JsonContainerDefinitionSerializerTest.php

+70-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Cspray\AnnotatedContainer;
44

5+
use Cspray\AnnotatedContainerFixture\ConfigurationWithArrayEnum\FooEnum;
6+
use Cspray\AnnotatedContainerFixture\ConfigurationWithAssocArrayEnum;
57
use Cspray\AnnotatedContainerFixture\ConfigurationWithEnum\MyEnum;
68
use Cspray\AnnotatedContainerFixture\Fixtures;
79
use Cspray\AnnotatedTarget\PhpParserAnnotatedTargetParser;
@@ -248,18 +250,46 @@ public function testSerializeWithEnumInjectDefinitions() {
248250
], $json['injectDefinitions']);
249251
}
250252

253+
public function testSerializeWithArrayEnumInjectDefinitions() {
254+
$serializer = new JsonContainerDefinitionSerializer();
255+
$containerDefinition = $this->containerDefinitionCompiler->compile(
256+
ContainerDefinitionCompileOptionsBuilder::scanDirectories(Fixtures::configurationWithArrayEnum()->getPath())->build()
257+
);
258+
259+
$json = json_decode($serializer->serialize($containerDefinition), true);
260+
self::assertArrayHasKey('injectDefinitions', $json);
261+
self::assertContains([
262+
'injectTargetType' => Fixtures::configurationWithArrayEnum()->myConfiguration()->getName(),
263+
'injectTargetMethod' => null,
264+
'injectTargetName' => 'cases',
265+
'type' => 'array',
266+
'value' => [
267+
[
268+
'type' => FooEnum::class,
269+
'value' => 'Bar'
270+
],
271+
[
272+
'type' => FooEnum::class,
273+
'value' => 'Qux'
274+
]
275+
],
276+
'profiles' => ['default'],
277+
'storeName' => null
278+
], $json['injectDefinitions']);
279+
}
280+
251281
/** ======================================== Deserialization Testing ==============================================*/
252282

253283
public function serializeDeserializeSerializeDirs() : array {
254284
return [
255-
[Fixtures::singleConcreteService()->getPath()],
256-
[Fixtures::delegatedService()->getPath()],
257-
[Fixtures::interfacePrepareServices()->getPath()],
258-
[Fixtures::profileResolvedServices()->getPath()],
259-
[Fixtures::abstractClassAliasedService()->getPath()],
260-
[Fixtures::namedServices()->getPath()],
261-
[Fixtures::injectConstructorServices()->getPath()],
262-
[Fixtures::configurationServices()->getPath()]
285+
'singleConcrete' => [Fixtures::singleConcreteService()->getPath()],
286+
'delegatedService' => [Fixtures::delegatedService()->getPath()],
287+
'interfacePrepareServices' => [Fixtures::interfacePrepareServices()->getPath()],
288+
'profileResolvedServices' => [Fixtures::profileResolvedServices()->getPath()],
289+
'abstractClassAliasedService' => [Fixtures::abstractClassAliasedService()->getPath()],
290+
'namedServices' => [Fixtures::namedServices()->getPath()],
291+
'injectConstructorServices' => [Fixtures::injectConstructorServices()->getPath()],
292+
'configurationServices' => [Fixtures::configurationServices()->getPath()]
263293
];
264294
}
265295

@@ -363,6 +393,38 @@ public function testDeserializeWithEnum() {
363393
self::assertSame(MyEnum::Foo, $injectDefinitions[0]->getValue());
364394
}
365395

396+
public function testDeserializeWithArrayEnum() {
397+
$serializer = new JsonContainerDefinitionSerializer();
398+
$containerDefinition = $this->containerDefinitionCompiler->compile(
399+
ContainerDefinitionCompileOptionsBuilder::scanDirectories(Fixtures::configurationWithArrayEnum()->getPath())->build()
400+
);
401+
402+
$serialized = $serializer->serialize($containerDefinition);
403+
$subjectDefinition = $serializer->deserialize($serialized);
404+
405+
$injectDefinitions = $subjectDefinition->getInjectDefinitions();
406+
407+
self::assertCount(1, $injectDefinitions);
408+
self::assertSame([FooEnum::Bar, FooEnum::Qux], $injectDefinitions[0]->getValue());
409+
}
410+
411+
public function testDeserializeWithAssocArrayEnum() {
412+
$serializer = new JsonContainerDefinitionSerializer();
413+
$containerDefinition = $this->containerDefinitionCompiler->compile(
414+
ContainerDefinitionCompileOptionsBuilder::scanDirectories(Fixtures::configurationWithAssocArrayEnum()->getPath())->build()
415+
);
416+
417+
$serialized = $serializer->serialize($containerDefinition);
418+
$subjectDefinition = $serializer->deserialize($serialized);
419+
420+
$injectDefinitions = $subjectDefinition->getInjectDefinitions();
421+
422+
self::assertCount(1, $injectDefinitions);
423+
self::assertSame([
424+
'b' => ConfigurationWithAssocArrayEnum\MyEnum::B,
425+
'c' => ConfigurationWithAssocArrayEnum\MyEnum::C
426+
], $injectDefinitions[0]->getValue());
427+
}
366428
}
367429

368430

0 commit comments

Comments
 (0)