From f962669ec9fcb3e3e1d8f29d557df642f6c44db5 Mon Sep 17 00:00:00 2001 From: David Young Date: Sun, 30 Apr 2023 16:34:43 -0500 Subject: [PATCH] Renamed IBodyNegotiator to IBodyDeserializer and removed some code that was violating DRY (#255) * Changed name of IBodyNegotiator to IBodyDeserializer and BodyNegotiator to NegotiatedBodyDeserializer to be a bit more specific * Added support for deserializing null bodies as empty arrays, updated PHP-CS-Fixer to sort elements alphabetically * Updated various API components to use IBodyDeserializer where appropriate * Updated static analysis to order alphabetically elements at the same visibility --- .php-cs-fixer.dist.php | 2 +- .../Metadata/BinderMetadataCollection.php | 4 +- .../ContainerBinderMetadataCollector.php | 8 +- src/Container.php | 8 +- .../BinderMetadataCollectionFactoryTest.php | 114 +++++++++--------- .../Metadata/BinderMetadataCollectionTest.php | 36 +++--- tests/Binders/Metadata/BoundInterfaceTest.php | 11 +- .../Metadata/ResolvedInterfaceTest.php | 15 ++- tests/ContainerTest.php | 14 +-- .../ConstructorWithDefaultValuePrimitives.php | 4 +- tests/Mocks/ConstructorWithPrimitives.php | 4 +- tests/Mocks/ConstructorWithSetters.php | 8 +- 12 files changed, 113 insertions(+), 115 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 28c234c..c7e1b99 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -74,7 +74,7 @@ 'method_protected', 'method_private_static', 'method_private' - ] + ], ], 'ordered_imports' => true, 'return_type_declaration' => ['space_before' => 'none'], diff --git a/src/Binders/Metadata/BinderMetadataCollection.php b/src/Binders/Metadata/BinderMetadataCollection.php index 3b56df5..09c7a3b 100644 --- a/src/Binders/Metadata/BinderMetadataCollection.php +++ b/src/Binders/Metadata/BinderMetadataCollection.php @@ -17,10 +17,10 @@ */ final class BinderMetadataCollection { - /** @var array> The mapping of interfaces to binder metadata that universally resolve those interfaces */ - private array $universalResolutions = []; /** @var array>> The mapping of targets to interfaces to binder metadata that resolve the interface for the target */ private array $targetedResolutions = []; + /** @var array> The mapping of interfaces to binder metadata that universally resolve those interfaces */ + private array $universalResolutions = []; /** * @param list $binderMetadatas The list of all binder metadata diff --git a/src/Binders/Metadata/ContainerBinderMetadataCollector.php b/src/Binders/Metadata/ContainerBinderMetadataCollector.php index 9c1ad24..b38ae7c 100644 --- a/src/Binders/Metadata/ContainerBinderMetadataCollector.php +++ b/src/Binders/Metadata/ContainerBinderMetadataCollector.php @@ -25,12 +25,12 @@ */ final class ContainerBinderMetadataCollector implements IBinderMetadataCollector, IContainer { - /** @var Context The current context */ - private Context $currentContext; - /** @var list The stack of contexts */ - private array $contextStack = []; /** @var list The list of bound interfaces that were found */ private array $boundInterfaces = []; + /** @var list The stack of contexts */ + private array $contextStack = []; + /** @var Context The current context */ + private Context $currentContext; /** @var list The list of resolved interfaces that were found */ private array $resolvedInterfaces = []; diff --git a/src/Container.php b/src/Container.php index 974bbb3..2ebc0c3 100644 --- a/src/Container.php +++ b/src/Container.php @@ -36,14 +36,14 @@ class Container implements IContainer * @var Container|null */ public static ?Container $globalInstance = null; - /** @var Context The current context */ - protected Context $currentContext; - /** @var list The stack of contexts */ - protected array $contextStack = []; /** @var array>> The list of bindings */ protected array $bindings = []; /** @var array|null}> The cache of reflection constructors and their parameters */ protected array $constructorReflectionCache = []; + /** @var list The stack of contexts */ + protected array $contextStack = []; + /** @var Context The current context */ + protected Context $currentContext; public function __construct() { diff --git a/tests/Binders/Metadata/BinderMetadataCollectionFactoryTest.php b/tests/Binders/Metadata/BinderMetadataCollectionFactoryTest.php index 0bc35a0..1b5eab1 100644 --- a/tests/Binders/Metadata/BinderMetadataCollectionFactoryTest.php +++ b/tests/Binders/Metadata/BinderMetadataCollectionFactoryTest.php @@ -55,6 +55,31 @@ public function bind(IContainer $container): void $this->assertEquals($expectedCollection, $actualCollection); } + public function testCreatingCollectionThatMustRetryBinderKeepsTrackOfResolutionsThatWorkedTheSecondTime(): void + { + $this->expectException(ImpossibleBindingException::class); + $binderA = new class () extends Binder { + public function bind(IContainer $container): void + { + // This will fail the first time, but should pass the second time + $container->resolve(IFoo::class); + $container->bindClass(IPerson::class, Dave::class); + // This will continue to not be able to be resolved + $container->resolve(IBar::class); + } + }; + $binderB = new class () extends Binder { + public function bind(IContainer $container): void + { + $container->bindInstance(IFoo::class, new Foo()); + } + }; + $this->expectExceptionMessage( + (new ImpossibleBindingException([IBar::class => [$binderA]]))->getMessage(), + ); + $this->factory->createBinderMetadataCollection([$binderA, $binderB]); + } + public function testCreatingCollectionThatNeedsTargetedBindingWorksWhenOneHasUniversalBinding(): void { $target = new class () { @@ -134,6 +159,38 @@ public function bind(IContainer $container): void $this->assertEquals($expectedCollection, $actualCollection); } + public function testCreatingCollectionThatReliesOnMultipleOtherBindersBindingStillWorks(): void + { + $binderA = new class () extends Binder { + public function bind(IContainer $container): void + { + $container->resolve(IFoo::class); + $container->bindClass(IPerson::class, Dave::class); + $container->resolve(IBar::class); + } + }; + $binderB = new class () extends Binder { + public function bind(IContainer $container): void + { + $container->bindInstance(IFoo::class, new Foo()); + } + }; + $binderC = new class () extends Binder { + public function bind(IContainer $container): void + { + $container->bindInstance(IBar::class, new Bar()); + } + }; + // Binder B will be before binder A because it isn't dependent on another binder's bindings + $expectedCollection = new BinderMetadataCollection([ + new BinderMetadata($binderB, [new BoundInterface(IFoo::class, new UniversalContext())], []), + new BinderMetadata($binderC, [new BoundInterface(IBar::class, new UniversalContext())], []), + new BinderMetadata($binderA, [new BoundInterface(IPerson::class, new UniversalContext())], [new ResolvedInterface(IFoo::class, new UniversalContext()), new ResolvedInterface(IBar::class, new UniversalContext())]) + ]); + $actualCollection = $this->factory->createBinderMetadataCollection([$binderA, $binderB, $binderC]); + $this->assertEquals($expectedCollection, $actualCollection); + } + public function testCreatingCollectionThatReliesOnTargetedBindingSetInAnotherStillWorks(): void { $target = new class () { @@ -170,63 +227,6 @@ public function bind(IContainer $container): void $this->assertEquals($expectedCollection, $actualCollection); } - public function testCreatingCollectionThatMustRetryBinderKeepsTrackOfResolutionsThatWorkedTheSecondTime(): void - { - $this->expectException(ImpossibleBindingException::class); - $binderA = new class () extends Binder { - public function bind(IContainer $container): void - { - // This will fail the first time, but should pass the second time - $container->resolve(IFoo::class); - $container->bindClass(IPerson::class, Dave::class); - // This will continue to not be able to be resolved - $container->resolve(IBar::class); - } - }; - $binderB = new class () extends Binder { - public function bind(IContainer $container): void - { - $container->bindInstance(IFoo::class, new Foo()); - } - }; - $this->expectExceptionMessage( - (new ImpossibleBindingException([IBar::class => [$binderA]]))->getMessage(), - ); - $this->factory->createBinderMetadataCollection([$binderA, $binderB]); - } - - public function testCreatingCollectionThatReliesOnMultipleOtherBindersBindingStillWorks(): void - { - $binderA = new class () extends Binder { - public function bind(IContainer $container): void - { - $container->resolve(IFoo::class); - $container->bindClass(IPerson::class, Dave::class); - $container->resolve(IBar::class); - } - }; - $binderB = new class () extends Binder { - public function bind(IContainer $container): void - { - $container->bindInstance(IFoo::class, new Foo()); - } - }; - $binderC = new class () extends Binder { - public function bind(IContainer $container): void - { - $container->bindInstance(IBar::class, new Bar()); - } - }; - // Binder B will be before binder A because it isn't dependent on another binder's bindings - $expectedCollection = new BinderMetadataCollection([ - new BinderMetadata($binderB, [new BoundInterface(IFoo::class, new UniversalContext())], []), - new BinderMetadata($binderC, [new BoundInterface(IBar::class, new UniversalContext())], []), - new BinderMetadata($binderA, [new BoundInterface(IPerson::class, new UniversalContext())], [new ResolvedInterface(IFoo::class, new UniversalContext()), new ResolvedInterface(IBar::class, new UniversalContext())]) - ]); - $actualCollection = $this->factory->createBinderMetadataCollection([$binderA, $binderB, $binderC]); - $this->assertEquals($expectedCollection, $actualCollection); - } - public function testCreatingCollectionWithBinderThatCannotResolveSomethingThrowsException(): void { $this->expectException(ImpossibleBindingException::class); diff --git a/tests/Binders/Metadata/BinderMetadataCollectionTest.php b/tests/Binders/Metadata/BinderMetadataCollectionTest.php index 7e46850..78b37b1 100644 --- a/tests/Binders/Metadata/BinderMetadataCollectionTest.php +++ b/tests/Binders/Metadata/BinderMetadataCollectionTest.php @@ -39,7 +39,7 @@ public function testBinderThatResolvesTargetedInterfaceIsNotReturnedForTargetedB $this->assertEmpty($collection->getBinderMetadataThatResolveInterface(new BoundInterface($boundInterface::class, new TargetedContext($target::class)))); } - public function testBinderThatResolvesTargetedInterfaceIsReturnedForUniversalBoundInterfaceWithSameInterface(): void + public function testBinderThatResolvesTargetedInterfaceIsReturnedForTargetedBoundInterfaceWithSameInterfaceAndTarget(): void { $resolvedInterface = new class () { }; @@ -49,12 +49,14 @@ public function testBinderThatResolvesTargetedInterfaceIsReturnedForUniversalBou new BinderMetadata($this->createMockBinder(), [], [new ResolvedInterface($resolvedInterface::class, new TargetedContext($target::class))]) ]; $collection = new BinderMetadataCollection($binderMetadatas); - $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface(new BoundInterface($resolvedInterface::class, new UniversalContext())); + $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface( + new BoundInterface($resolvedInterface::class, new TargetedContext($target::class)) + ); $this->assertCount(1, $actualBinderMetadatas); $this->assertSame($binderMetadatas[0], $actualBinderMetadatas[0]); } - public function testBinderThatResolvesTargetedInterfaceIsReturnedForTargetedBoundInterfaceWithSameInterfaceAndTarget(): void + public function testBinderThatResolvesTargetedInterfaceIsReturnedForUniversalBoundInterfaceWithSameInterface(): void { $resolvedInterface = new class () { }; @@ -64,58 +66,56 @@ public function testBinderThatResolvesTargetedInterfaceIsReturnedForTargetedBoun new BinderMetadata($this->createMockBinder(), [], [new ResolvedInterface($resolvedInterface::class, new TargetedContext($target::class))]) ]; $collection = new BinderMetadataCollection($binderMetadatas); - $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface( - new BoundInterface($resolvedInterface::class, new TargetedContext($target::class)) - ); + $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface(new BoundInterface($resolvedInterface::class, new UniversalContext())); $this->assertCount(1, $actualBinderMetadatas); $this->assertSame($binderMetadatas[0], $actualBinderMetadatas[0]); } - public function testBinderThatUniversallyResolvesInterfaceIsNotReturnedForUniversalBoundInterfaceWithDifferentInterface(): void + public function testBinderThatUniversallyResolvesInterfaceIsNotReturnedForTargetedBoundInterfaceWithSameInterface(): void { $resolvedInterface = new class () { }; - $boundInterface = new class () { + $target = new class () { }; $binderMetadatas = [ new BinderMetadata($this->createMockBinder(), [], [new ResolvedInterface($resolvedInterface::class, new UniversalContext())]) ]; $collection = new BinderMetadataCollection($binderMetadatas); $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface( - new BoundInterface($boundInterface::class, new UniversalContext()) + new BoundInterface($resolvedInterface::class, new TargetedContext($target::class)) ); $this->assertEmpty($actualBinderMetadatas); } - public function testBinderThatUniversallyResolvesInterfaceIsReturnedForUniversalBoundInterfaceWithSameInterface(): void + public function testBinderThatUniversallyResolvesInterfaceIsNotReturnedForUniversalBoundInterfaceWithDifferentInterface(): void { $resolvedInterface = new class () { }; + $boundInterface = new class () { + }; $binderMetadatas = [ new BinderMetadata($this->createMockBinder(), [], [new ResolvedInterface($resolvedInterface::class, new UniversalContext())]) ]; $collection = new BinderMetadataCollection($binderMetadatas); $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface( - new BoundInterface($resolvedInterface::class, new UniversalContext()) + new BoundInterface($boundInterface::class, new UniversalContext()) ); - $this->assertCount(1, $actualBinderMetadatas); - $this->assertSame($binderMetadatas[0], $actualBinderMetadatas[0]); + $this->assertEmpty($actualBinderMetadatas); } - public function testBinderThatUniversallyResolvesInterfaceIsNotReturnedForTargetedBoundInterfaceWithSameInterface(): void + public function testBinderThatUniversallyResolvesInterfaceIsReturnedForUniversalBoundInterfaceWithSameInterface(): void { $resolvedInterface = new class () { }; - $target = new class () { - }; $binderMetadatas = [ new BinderMetadata($this->createMockBinder(), [], [new ResolvedInterface($resolvedInterface::class, new UniversalContext())]) ]; $collection = new BinderMetadataCollection($binderMetadatas); $actualBinderMetadatas = $collection->getBinderMetadataThatResolveInterface( - new BoundInterface($resolvedInterface::class, new TargetedContext($target::class)) + new BoundInterface($resolvedInterface::class, new UniversalContext()) ); - $this->assertEmpty($actualBinderMetadatas); + $this->assertCount(1, $actualBinderMetadatas); + $this->assertSame($binderMetadatas[0], $actualBinderMetadatas[0]); } public function testGetAllBinderMetadataReturnsAllMetadata(): void diff --git a/tests/Binders/Metadata/BoundInterfaceTest.php b/tests/Binders/Metadata/BoundInterfaceTest.php index 11f1800..a097e49 100644 --- a/tests/Binders/Metadata/BoundInterfaceTest.php +++ b/tests/Binders/Metadata/BoundInterfaceTest.php @@ -19,12 +19,6 @@ class BoundInterfaceTest extends TestCase { - public function testGetInterfaceReturnsSetInterface(): void - { - $interface = new BoundInterface(self::class, new UniversalContext()); - $this->assertSame(self::class, $interface->interface); - } - public function testGetContextReturnsSetContext(): void { $target = new class () { @@ -33,4 +27,9 @@ public function testGetContextReturnsSetContext(): void $interface = new BoundInterface(self::class, $expectedContext); $this->assertSame($expectedContext, $interface->context); } + public function testGetInterfaceReturnsSetInterface(): void + { + $interface = new BoundInterface(self::class, new UniversalContext()); + $this->assertSame(self::class, $interface->interface); + } } diff --git a/tests/Binders/Metadata/ResolvedInterfaceTest.php b/tests/Binders/Metadata/ResolvedInterfaceTest.php index 94901c3..2bafd90 100644 --- a/tests/Binders/Metadata/ResolvedInterfaceTest.php +++ b/tests/Binders/Metadata/ResolvedInterfaceTest.php @@ -19,14 +19,6 @@ class ResolvedInterfaceTest extends TestCase { - public function testGetInterfaceReturnsSetInterface(): void - { - $resolvedInterface = new class () { - }; - $interface = new ResolvedInterface($resolvedInterface::class, new UniversalContext()); - $this->assertSame($resolvedInterface::class, $interface->interface); - } - public function testGetContextReturnsSetContext(): void { $target = new class () { @@ -37,4 +29,11 @@ public function testGetContextReturnsSetContext(): void $interface = new ResolvedInterface($resolvedInterface::class, $expectedContext); $this->assertSame($expectedContext, $interface->context); } + public function testGetInterfaceReturnsSetInterface(): void + { + $resolvedInterface = new class () { + }; + $interface = new ResolvedInterface($resolvedInterface::class, new UniversalContext()); + $this->assertSame($resolvedInterface::class, $interface->interface); + } } diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php index d32fdfd..ad34ced 100644 --- a/tests/ContainerTest.php +++ b/tests/ContainerTest.php @@ -127,13 +127,6 @@ public function testCallingClosureWithPrimitiveTypes(): void $this->assertSame('foo', $result); } - public function testCallingMethodWithPrimitiveTypesWithoutSpecifyingValue(): void - { - $this->expectException(CallException::class); - $instance = new ConstructorWithSetters(); - $this->container->callMethod($instance, 'setPrimitive'); - } - public function testCallingClosureWithTypeHintedAndPrimitiveTypes(): void { $this->container->bindClass(IFoo::class, Bar::class, [], true); @@ -166,6 +159,13 @@ public function testCallingClosureWithUnresolvableParametersThrowsException(): v $this->container->callClosure(fn (IFoo $foo) => null); } + public function testCallingMethodWithPrimitiveTypesWithoutSpecifyingValue(): void + { + $this->expectException(CallException::class); + $instance = new ConstructorWithSetters(); + $this->container->callMethod($instance, 'setPrimitive'); + } + public function testCallingNonExistentMethod(): void { $this->expectException(CallException::class); diff --git a/tests/Mocks/ConstructorWithDefaultValuePrimitives.php b/tests/Mocks/ConstructorWithDefaultValuePrimitives.php index cd55336..cb4401f 100644 --- a/tests/Mocks/ConstructorWithDefaultValuePrimitives.php +++ b/tests/Mocks/ConstructorWithDefaultValuePrimitives.php @@ -17,10 +17,10 @@ */ class ConstructorWithDefaultValuePrimitives { - /** @var string A primitive stored by this class */ - private string $foo; /** @var string A primitive stored by this class */ private string $bar; + /** @var string A primitive stored by this class */ + private string $foo; /** * @param string $foo A primitive to store in this class diff --git a/tests/Mocks/ConstructorWithPrimitives.php b/tests/Mocks/ConstructorWithPrimitives.php index 976317f..194e190 100644 --- a/tests/Mocks/ConstructorWithPrimitives.php +++ b/tests/Mocks/ConstructorWithPrimitives.php @@ -17,10 +17,10 @@ */ class ConstructorWithPrimitives { - /** @var string A primitive stored by this class */ - private string $foo; /** @var string A primitive stored by this class */ private string $bar; + /** @var string A primitive stored by this class */ + private string $foo; /** * @param string $foo A primitive to store in this class diff --git a/tests/Mocks/ConstructorWithSetters.php b/tests/Mocks/ConstructorWithSetters.php index 7da8352..8e07757 100644 --- a/tests/Mocks/ConstructorWithSetters.php +++ b/tests/Mocks/ConstructorWithSetters.php @@ -17,12 +17,12 @@ */ class ConstructorWithSetters { - /** @var string A primitive */ - private string $primitive = ''; - /** @var IFoo An interface dependency */ - private IFoo $interface; /** @var Bar A concrete dependency */ private Bar $concrete; + /** @var IFoo An interface dependency */ + private IFoo $interface; + /** @var string A primitive */ + private string $primitive = ''; /** * @return Bar