From 1f1b0e65bcf60b2ab1c130951fe407aabafcca90 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 29 Jul 2023 13:26:43 +0300 Subject: [PATCH 1/9] Add ServiceDefinition --- src/ServiceDefinition.php | 71 ++++ tests/Unit/ServiceDefinitionTest.php | 498 +++++++++++++++++++++++++++ 2 files changed, 569 insertions(+) create mode 100644 src/ServiceDefinition.php create mode 100644 tests/Unit/ServiceDefinitionTest.php diff --git a/src/ServiceDefinition.php b/src/ServiceDefinition.php new file mode 100644 index 0000000..34c3b0d --- /dev/null +++ b/src/ServiceDefinition.php @@ -0,0 +1,71 @@ +class = $class; + return $definition; + } + + public function constructor(array $arguments): self + { + $this->constructorArguments = $arguments; + return $this; + } + + public function callMethod(string $method, array $arguments = []): self + { + $this->calls[$method] = $arguments; + return $this; + } + + public function callMethods(array $properties): self + { + foreach ($properties as $property => $value) { + $this->callMethod($property, $value); + } + return $this; + } + + public function setProperty(string $property, mixed $value): self + { + $this->calls[$property] = $value; + return $this; + } + + public function setProperties(array $properties): self + { + foreach ($properties as $property => $value) { + $this->setProperty($property, $value); + } + return $this; + } + + public function resolve(ContainerInterface $container): mixed + { + $config = [ + ArrayDefinition::CLASS_NAME => $this->class, + ArrayDefinition::CONSTRUCTOR => $this->constructorArguments, + ...$this->calls, + ]; + return ArrayDefinition::fromConfig($config)->resolve($container); + } + + public function merge(self $other) + { + // TBD + } +} diff --git a/tests/Unit/ServiceDefinitionTest.php b/tests/Unit/ServiceDefinitionTest.php new file mode 100644 index 0000000..1645359 --- /dev/null +++ b/tests/Unit/ServiceDefinitionTest.php @@ -0,0 +1,498 @@ +resolve($container)); + } + + public function dataConstructor(): array + { + return [ + [null, null, []], + ['Kiradzu', null, ['Kiradzu']], + ['Kiradzu', null, ['name' => 'Kiradzu']], + ['Kiradzu', '2.0', ['Kiradzu', '2.0']], + ['Kiradzu', '2.0', ['name' => 'Kiradzu', 'version' => '2.0']], + ]; + } + + /** + * @dataProvider dataConstructor + */ + public function testConstructor(?string $name, ?string $version, array $constructorArguments): void + { + $container = new SimpleContainer(); + + $definition = ServiceDefinition::for(Phone::class) + ->constructor($constructorArguments); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($name, $phone->getName()); + self::assertSame($version, $phone->getVersion()); + } + + public function testConstructorWithVariadicAndIntKeys(): void + { + $container = new SimpleContainer(); + + $colors = ['red', 'green', 'blue']; + + $definition = ServiceDefinition::for(Phone::class) + ->constructor([ + null, + null, + $colors[0], + $colors[1], + $colors[2], + ]); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($colors, $phone->getColors()); + } + + public function testConstructorWithVariadicArrayAndIntKeys(): void + { + $container = new SimpleContainer(); + + $colors = ['red', 'green', 'blue']; + + $definition = ServiceDefinition::for(Phone::class) + ->constructor([ + null, + null, + $colors, + ]); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + + self::assertSame([$colors], $phone->getColors()); + } + + public function testConstructorWithVariadicAndNamedKeys(): void + { + $container = new SimpleContainer(); + + $colors = ['red', 'green', 'blue']; + $definition = ServiceDefinition::for(Phone::class) + ->constructor([ + 'name' => null, + 'version' => null, + 'colors' => $colors, + ]); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($colors, $phone->getColors()); + } + + public function testConstructorWithWrongVariadicArgument(): void + { + $container = new SimpleContainer(); + + $colors = 'red'; + $definition = ServiceDefinition::for(Phone::class) + ->constructor([ + 'name' => null, + 'version' => null, + 'colors' => $colors, + ]); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Named argument for a variadic parameter should be an array, "string" given.'); + + $definition->resolve($container); + } + + public function dataSetProperties(): array + { + return [ + [false, null, []], + [true, null, ['$dev' => true]], + [true, 'Radar', ['$dev' => true, '$codeName' => 'Radar']], + ]; + } + + /** + * @dataProvider dataSetProperties + */ + public function testSetProperties(bool $dev, ?string $codeName, array $setProperties): void + { + $container = new SimpleContainer(); + + $definition = ServiceDefinition::for(Phone::class) + ->setProperties($setProperties); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($dev, $phone->dev); + self::assertSame($codeName, $phone->codeName); + } + + public function dataCallMethods(): array + { + return [ + [null, [], []], + ['s43g23456', [], ['setId()' => ['s43g23456']]], + ['777', [], ['setId777()' => []]], + [ + '777', + [['Browser', null]], + [ + 'addApp()' => ['Browser'], + 'setId777()' => [], + ], + ], + [ + '42', + [['Browser', '7']], + [ + 'setId()' => ['42'], + 'addApp()' => ['Browser', '7'], + ], + ], + [ + null, + [['Browser', '7']], + [ + 'addApp()' => ['name' => 'Browser', 'version' => '7'], + ], + ], + ]; + } + + /** + * @dataProvider dataCallMethods + */ + public function testCallMethods(?string $id, array $apps, array $callMethods): void + { + $container = new SimpleContainer(); + + $definition = ServiceDefinition::for(Phone::class) + ->callMethods($callMethods); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($id, $phone->getId()); + self::assertSame($apps, $phone->getApps()); + } + + public function testCallFluentMethod(): void + { + $container = new SimpleContainer(); + + $author = 'Sergei'; + $country = 'Russia'; + $definition = ServiceDefinition::for(Phone::class) + ->callMethod('withAuthor()', [$author]) + ->callMethod('withCountry()', [$country]); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($author, $phone->getAuthor()); + self::assertSame($country, $phone->getCountry()); + } + + public function dataMethodAutowiring(): array + { + return [ + [ + 'kitty', + EngineMarkOne::class, + ['kitty'], + ], + [ + 'kitty', + EngineMarkOne::class, + ['name' => 'kitty'], + ], + [ + 'kitty', + EngineMarkTwo::class, + ['kitty', new EngineMarkTwo()], + ], + [ + 'kitty', + EngineMarkTwo::class, + ['name' => 'kitty', 'engine' => new EngineMarkTwo()], + ], + [ + 'kitty', + EngineMarkTwo::class, + ['kitty', Reference::to('mark2')], + ], + ]; + } + + /** + * @dataProvider dataMethodAutowiring + */ + public function testMethodAutowiring(?string $expectedName, ?string $expectedEngine, array $data): void + { + $container = new SimpleContainer([ + EngineInterface::class => new EngineMarkOne(), + 'mark2' => new EngineMarkTwo(), + ]); + + $definition = ServiceDefinition::for(Mouse::class) + ->callMethod('setNameAndEngine()', $data); + + /** @var Mouse $mouse */ + $mouse = $definition->resolve($container); + + self::assertSame($expectedName, $mouse->getName()); + self::assertInstanceOf($expectedEngine, $mouse->getEngine()); + } + + public function dataMethodVariadic(): array + { + return [ + [ + 'kitty', + [], + ['kitty'], + ], + [ + 'kitty', + [], + ['name' => 'kitty'], + ], + [ + 'kitty', + [], + ['name' => 'kitty', 'colors' => []], + ], + [ + 'kitty', + [1, 2, 3], + ['name' => 'kitty', 'colors' => [1, 2, 3]], + ], + [ + 'kitty', + [1, 2, 3], + ['kitty', 1, 2, 3], + ], + [ + 'kitty', + [[1, 2, 3]], + ['kitty', [1, 2, 3]], + ], + [ + 'kitty', + [1, 2, 3], + ['name' => 'kitty', 'colors' => Reference::to('data')], + ['data' => [1, 2, 3]], + ], + [ + 'kitty', + [[1, 2, 3]], + ['kitty', Reference::to('data')], + ['data' => [1, 2, 3]], + ], + ]; + } + + /** + * @dataProvider dataMethodVariadic + */ + public function testMethodVariadic( + ?string $expectedName, + array $expectedColors, + array $data, + array $containerDefinitions = [] + ): void { + $container = new SimpleContainer($containerDefinitions); + + $definition = ServiceDefinition::for(Mouse::class) + ->callMethod('setNameAndColors()', $data); + + /** @var Mouse $mouse */ + $mouse = $definition->resolve($container); + + self::assertSame($expectedName, $mouse->getName()); + self::assertSame($expectedColors, $mouse->getColors()); + } + + public function testArgumentsIndexedBothByNameAndByPositionInMethod(): void + { + $definition = ServiceDefinition::for(Mouse::class) + ->callMethod('setNameAndEngine()', ['kitty', 'engine' => new EngineMarkOne()]); + + $container = new SimpleContainer(); + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage( + 'Arguments indexed both by name and by position are not allowed in the same array.' + ); + $definition->resolve($container); + } + + public function testMethodWithWrongVariadicArgument(): void + { + $container = new SimpleContainer(); + + $definition = ServiceDefinition::for(Mouse::class) + ->callMethod('setNameAndColors()', [ + 'name' => 'kitty', + 'colors' => 'red', + ]); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Named argument for a variadic parameter should be an array, "string" given.'); + $definition->resolve($container); + } + + public function testMethodWithWrongReferenceVariadicArgument(): void + { + $container = new SimpleContainer([ + 'data' => 32, + ]); + + $definition = ServiceDefinition::for(Mouse::class) + ->callMethod('setNameAndColors()', [ + 'name' => 'kitty', + 'colors' => Reference::to('data'), + ]); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Named argument for a variadic parameter should be an array, "integer" given.'); + $definition->resolve($container); + } + + public function testMerge(): void + { + $this->markTestSkipped('TBD'); + + $a = ServiceDefinition::for(Phone::class) + ->constructor( ['version' => '2.0']) + ->setProperty('$codeName', 'a') + ->callMethod('setColors()', ['red', 'green'],); + + $b = ServiceDefinition::for(Phone::class) + ->constructor(['name' => 'Retro', 'version' => '1.0']) + ->setProperty('$dev', true) + ->setProperty('$codeName', 'b') + ->callMethod('setId()', [42]) + ->callMethod('setColors()', ['yellow']); + + $c = $a->merge($b); + + $this->assertSame(Phone::class, $c->getClass()); + $this->assertSame(['name' => 'Retro', 'version' => '2.0'], $c->getConstructorArguments()); + $this->assertSame( + [ + '$codeName' => [ArrayDefinition::TYPE_PROPERTY, 'codeName', 'b'], + 'setColors()' => [ArrayDefinition::TYPE_METHOD, 'setColors', ['yellow', 'green']], + '$dev' => [ArrayDefinition::TYPE_PROPERTY, 'dev', true], + 'setId()' => [ArrayDefinition::TYPE_METHOD, 'setId', [42]], + ], + $c->getMethodsAndProperties(), + ); + } + + public function testMergeImmutability(): void + { + $this->markTestSkipped('TBD'); + $a = ServiceDefinition::for(Phone::class); + $b = ServiceDefinition::for(Phone::class); + $c = $a->merge($b); + $this->assertNotSame($a, $c); + $this->assertNotSame($b, $c); + } + + public function testArgumentsIndexedBothByNameAndByPosition(): void + { + $definition = ServiceDefinition::for(Phone::class) + ->constructor(['name' => 'Taruto', '1.0']); + $container = new SimpleContainer(); + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage( + 'Arguments indexed both by name and by position are not allowed in the same array.' + ); + $definition->resolve($container); + } + + public function testReferenceContainer(): void + { + $this->markTestSkipped('TBD'); + $container = new SimpleContainer([ + EngineInterface::class => new EngineMarkOne(), + ]); + $referenceContainer = new SimpleContainer([ + ColorInterface::class => new ColorPink(), + EngineInterface::class => new EngineMarkTwo(), + ]); + + $definition = ServiceDefinition::for(Car::class) + ->callMethod('setColor()', [Reference::to(ColorInterface::class)]); + + $newDefinition = $definition->withReferenceContainer($referenceContainer); + + /** @var Car $object */ + $object = $newDefinition->resolve($container); + + $this->assertNotSame($definition, $newDefinition); + $this->assertInstanceOf(Car::class, $object); + $this->assertInstanceOf(EngineMarkOne::class, $object->getEngine()); + } + + public function testMagicMethods(): void + { + $definiton = ServiceDefinition::for(Recorder::class) + ->callMethod('first()') + ->setProperty('$second', null) + ->callMethod('third()', ['hello', true]) + ->setProperty('$fourth', 'hello'); + + $object = $definiton->resolve(new SimpleContainer()); + + $this->assertSame( + [ + 'Call first()', + 'Set $second to null', + 'Call third(string, bool)', + 'Set $fourth to string', + ], + $object->getEvents() + ); + } +} From e8148cef25d88b5a032fabc1782534988cee8e1a Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sat, 29 Jul 2023 10:26:56 +0000 Subject: [PATCH 2/9] Apply fixes from StyleCI --- tests/Unit/ServiceDefinitionTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Unit/ServiceDefinitionTest.php b/tests/Unit/ServiceDefinitionTest.php index 1645359..373b78e 100644 --- a/tests/Unit/ServiceDefinitionTest.php +++ b/tests/Unit/ServiceDefinitionTest.php @@ -131,7 +131,7 @@ public function testConstructorWithWrongVariadicArgument(): void 'name' => null, 'version' => null, 'colors' => $colors, - ]); + ]); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Named argument for a variadic parameter should be an array, "string" given.'); @@ -354,7 +354,7 @@ public function testMethodVariadic( public function testArgumentsIndexedBothByNameAndByPositionInMethod(): void { $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndEngine()', ['kitty', 'engine' => new EngineMarkOne()]); + ->callMethod('setNameAndEngine()', ['kitty', 'engine' => new EngineMarkOne()]); $container = new SimpleContainer(); @@ -370,7 +370,7 @@ public function testMethodWithWrongVariadicArgument(): void $container = new SimpleContainer(); $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndColors()', [ + ->callMethod('setNameAndColors()', [ 'name' => 'kitty', 'colors' => 'red', ]); @@ -387,7 +387,7 @@ public function testMethodWithWrongReferenceVariadicArgument(): void ]); $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndColors()', [ + ->callMethod('setNameAndColors()', [ 'name' => 'kitty', 'colors' => Reference::to('data'), ]); @@ -402,15 +402,15 @@ public function testMerge(): void $this->markTestSkipped('TBD'); $a = ServiceDefinition::for(Phone::class) - ->constructor( ['version' => '2.0']) + ->constructor(['version' => '2.0']) ->setProperty('$codeName', 'a') - ->callMethod('setColors()', ['red', 'green'],); + ->callMethod('setColors()', ['red', 'green'], ); $b = ServiceDefinition::for(Phone::class) ->constructor(['name' => 'Retro', 'version' => '1.0']) ->setProperty('$dev', true) ->setProperty('$codeName', 'b') - ->callMethod('setId()', [42]) + ->callMethod('setId()', [42]) ->callMethod('setColors()', ['yellow']); $c = $a->merge($b); From 46bba1e641e68965bccd6d3f0d5a95259a07abcd Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 31 Jul 2023 19:57:42 +0300 Subject: [PATCH 3/9] Rename methods --- src/ServiceDefinition.php | 27 ++++++------ tests/Unit/ServiceDefinitionTest.php | 66 ++++++++++++++-------------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/ServiceDefinition.php b/src/ServiceDefinition.php index 34c3b0d..8462eb2 100644 --- a/src/ServiceDefinition.php +++ b/src/ServiceDefinition.php @@ -10,14 +10,15 @@ final class ServiceDefinition implements DefinitionInterface { private array $constructorArguments = []; - private string $class; private array $calls = []; + private function __construct(private string $class) + { + } + public static function for(string $class): self { - $definition = new self(); - $definition->class = $class; - return $definition; + return new self($class); } public function constructor(array $arguments): self @@ -26,30 +27,30 @@ public function constructor(array $arguments): self return $this; } - public function callMethod(string $method, array $arguments = []): self + public function call(string $method, array $arguments = []): self { - $this->calls[$method] = $arguments; + $this->calls[$method . '()'] = $arguments; return $this; } - public function callMethods(array $properties): self + public function calls(array $methods): self { - foreach ($properties as $property => $value) { - $this->callMethod($property, $value); + foreach ($methods as $method => $arguments) { + $this->call($method, $arguments); } return $this; } - public function setProperty(string $property, mixed $value): self + public function set(string $property, mixed $value): self { - $this->calls[$property] = $value; + $this->calls['$' . $property] = $value; return $this; } - public function setProperties(array $properties): self + public function sets(array $properties): self { foreach ($properties as $property => $value) { - $this->setProperty($property, $value); + $this->set($property, $value); } return $this; } diff --git a/tests/Unit/ServiceDefinitionTest.php b/tests/Unit/ServiceDefinitionTest.php index 373b78e..de06f7e 100644 --- a/tests/Unit/ServiceDefinitionTest.php +++ b/tests/Unit/ServiceDefinitionTest.php @@ -143,8 +143,8 @@ public function dataSetProperties(): array { return [ [false, null, []], - [true, null, ['$dev' => true]], - [true, 'Radar', ['$dev' => true, '$codeName' => 'Radar']], + [true, null, ['dev' => true]], + [true, 'Radar', ['dev' => true, 'codeName' => 'Radar']], ]; } @@ -156,7 +156,7 @@ public function testSetProperties(bool $dev, ?string $codeName, array $setProper $container = new SimpleContainer(); $definition = ServiceDefinition::for(Phone::class) - ->setProperties($setProperties); + ->sets($setProperties); /** @var Phone $phone */ $phone = $definition->resolve($container); @@ -169,29 +169,29 @@ public function dataCallMethods(): array { return [ [null, [], []], - ['s43g23456', [], ['setId()' => ['s43g23456']]], - ['777', [], ['setId777()' => []]], + ['s43g23456', [], ['setId' => ['s43g23456']]], + ['777', [], ['setId777' => []]], [ '777', [['Browser', null]], [ - 'addApp()' => ['Browser'], - 'setId777()' => [], + 'addApp' => ['Browser'], + 'setId777' => [], ], ], [ '42', [['Browser', '7']], [ - 'setId()' => ['42'], - 'addApp()' => ['Browser', '7'], + 'setId' => ['42'], + 'addApp' => ['Browser', '7'], ], ], [ null, [['Browser', '7']], [ - 'addApp()' => ['name' => 'Browser', 'version' => '7'], + 'addApp' => ['name' => 'Browser', 'version' => '7'], ], ], ]; @@ -205,7 +205,7 @@ public function testCallMethods(?string $id, array $apps, array $callMethods): v $container = new SimpleContainer(); $definition = ServiceDefinition::for(Phone::class) - ->callMethods($callMethods); + ->calls($callMethods); /** @var Phone $phone */ $phone = $definition->resolve($container); @@ -221,8 +221,8 @@ public function testCallFluentMethod(): void $author = 'Sergei'; $country = 'Russia'; $definition = ServiceDefinition::for(Phone::class) - ->callMethod('withAuthor()', [$author]) - ->callMethod('withCountry()', [$country]); + ->call('withAuthor', [$author]) + ->call('withCountry', [$country]); /** @var Phone $phone */ $phone = $definition->resolve($container); @@ -273,7 +273,7 @@ public function testMethodAutowiring(?string $expectedName, ?string $expectedEng ]); $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndEngine()', $data); + ->call('setNameAndEngine', $data); /** @var Mouse $mouse */ $mouse = $definition->resolve($container); @@ -342,7 +342,7 @@ public function testMethodVariadic( $container = new SimpleContainer($containerDefinitions); $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndColors()', $data); + ->call('setNameAndColors', $data); /** @var Mouse $mouse */ $mouse = $definition->resolve($container); @@ -354,7 +354,7 @@ public function testMethodVariadic( public function testArgumentsIndexedBothByNameAndByPositionInMethod(): void { $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndEngine()', ['kitty', 'engine' => new EngineMarkOne()]); + ->call('setNameAndEngine', ['kitty', 'engine' => new EngineMarkOne()]); $container = new SimpleContainer(); @@ -370,7 +370,7 @@ public function testMethodWithWrongVariadicArgument(): void $container = new SimpleContainer(); $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndColors()', [ + ->call('setNameAndColors', [ 'name' => 'kitty', 'colors' => 'red', ]); @@ -387,7 +387,7 @@ public function testMethodWithWrongReferenceVariadicArgument(): void ]); $definition = ServiceDefinition::for(Mouse::class) - ->callMethod('setNameAndColors()', [ + ->call('setNameAndColors', [ 'name' => 'kitty', 'colors' => Reference::to('data'), ]); @@ -403,15 +403,15 @@ public function testMerge(): void $a = ServiceDefinition::for(Phone::class) ->constructor(['version' => '2.0']) - ->setProperty('$codeName', 'a') - ->callMethod('setColors()', ['red', 'green'], ); + ->set('codeName', 'a') + ->call('setColors', ['red', 'green'], ); $b = ServiceDefinition::for(Phone::class) ->constructor(['name' => 'Retro', 'version' => '1.0']) - ->setProperty('$dev', true) - ->setProperty('$codeName', 'b') - ->callMethod('setId()', [42]) - ->callMethod('setColors()', ['yellow']); + ->set('dev', true) + ->set('codeName', 'b') + ->call('setId', [42]) + ->call('setColors', ['yellow']); $c = $a->merge($b); @@ -419,10 +419,10 @@ public function testMerge(): void $this->assertSame(['name' => 'Retro', 'version' => '2.0'], $c->getConstructorArguments()); $this->assertSame( [ - '$codeName' => [ArrayDefinition::TYPE_PROPERTY, 'codeName', 'b'], - 'setColors()' => [ArrayDefinition::TYPE_METHOD, 'setColors', ['yellow', 'green']], - '$dev' => [ArrayDefinition::TYPE_PROPERTY, 'dev', true], - 'setId()' => [ArrayDefinition::TYPE_METHOD, 'setId', [42]], + 'codeName' => [ArrayDefinition::TYPE_PROPERTY, 'codeName', 'b'], + 'setColors' => [ArrayDefinition::TYPE_METHOD, 'setColors', ['yellow', 'green']], + 'dev' => [ArrayDefinition::TYPE_PROPERTY, 'dev', true], + 'setId' =>[ArrayDefinition::TYPE_METHOD, 'setId', [42]], ], $c->getMethodsAndProperties(), ); @@ -463,7 +463,7 @@ public function testReferenceContainer(): void ]); $definition = ServiceDefinition::for(Car::class) - ->callMethod('setColor()', [Reference::to(ColorInterface::class)]); + ->call('setColor', [Reference::to(ColorInterface::class)]); $newDefinition = $definition->withReferenceContainer($referenceContainer); @@ -478,10 +478,10 @@ public function testReferenceContainer(): void public function testMagicMethods(): void { $definiton = ServiceDefinition::for(Recorder::class) - ->callMethod('first()') - ->setProperty('$second', null) - ->callMethod('third()', ['hello', true]) - ->setProperty('$fourth', 'hello'); + ->call('first') + ->set('second', null) + ->call('third', ['hello', true]) + ->set('fourth', 'hello'); $object = $definiton->resolve(new SimpleContainer()); From 2026959e239b31ab6def8556fef0c5147a41306e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 31 Jul 2023 16:57:54 +0000 Subject: [PATCH 4/9] Apply fixes from StyleCI --- tests/Unit/ServiceDefinitionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/ServiceDefinitionTest.php b/tests/Unit/ServiceDefinitionTest.php index de06f7e..c7e4ebd 100644 --- a/tests/Unit/ServiceDefinitionTest.php +++ b/tests/Unit/ServiceDefinitionTest.php @@ -422,7 +422,7 @@ public function testMerge(): void 'codeName' => [ArrayDefinition::TYPE_PROPERTY, 'codeName', 'b'], 'setColors' => [ArrayDefinition::TYPE_METHOD, 'setColors', ['yellow', 'green']], 'dev' => [ArrayDefinition::TYPE_PROPERTY, 'dev', true], - 'setId' =>[ArrayDefinition::TYPE_METHOD, 'setId', [42]], + 'setId' => [ArrayDefinition::TYPE_METHOD, 'setId', [42]], ], $c->getMethodsAndProperties(), ); From 6c5b569391343dd8b3c96c38ba74572d46e9dfed Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Mon, 31 Jul 2023 20:03:10 +0300 Subject: [PATCH 5/9] Add a shortcut to set constructor --- src/ServiceDefinition.php | 15 ++++++++------- tests/Unit/ServiceDefinitionTest.php | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/ServiceDefinition.php b/src/ServiceDefinition.php index 8462eb2..58cf373 100644 --- a/src/ServiceDefinition.php +++ b/src/ServiceDefinition.php @@ -9,21 +9,22 @@ final class ServiceDefinition implements DefinitionInterface { - private array $constructorArguments = []; private array $calls = []; - private function __construct(private string $class) - { + private function __construct( + private string $class, + private array $constructor = [], + ) { } - public static function for(string $class): self + public static function for(string $class, array $constructor = []): self { - return new self($class); + return new self($class, $constructor); } public function constructor(array $arguments): self { - $this->constructorArguments = $arguments; + $this->constructor = $arguments; return $this; } @@ -59,7 +60,7 @@ public function resolve(ContainerInterface $container): mixed { $config = [ ArrayDefinition::CLASS_NAME => $this->class, - ArrayDefinition::CONSTRUCTOR => $this->constructorArguments, + ArrayDefinition::CONSTRUCTOR => $this->constructor, ...$this->calls, ]; return ArrayDefinition::fromConfig($config)->resolve($container); diff --git a/tests/Unit/ServiceDefinitionTest.php b/tests/Unit/ServiceDefinitionTest.php index de06f7e..2e5f56b 100644 --- a/tests/Unit/ServiceDefinitionTest.php +++ b/tests/Unit/ServiceDefinitionTest.php @@ -62,6 +62,22 @@ public function testConstructor(?string $name, ?string $version, array $construc self::assertSame($version, $phone->getVersion()); } + /** + * @dataProvider dataConstructor + */ + public function testShortConstructor(?string $name, ?string $version, array $constructorArguments): void + { + $container = new SimpleContainer(); + + $definition = ServiceDefinition::for(Phone::class, $constructorArguments); + + /** @var Phone $phone */ + $phone = $definition->resolve($container); + + self::assertSame($name, $phone->getName()); + self::assertSame($version, $phone->getVersion()); + } + public function testConstructorWithVariadicAndIntKeys(): void { $container = new SimpleContainer(); From 6936a5aea55966d39e439570bc2d0f6d57cefce6 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Wed, 2 Aug 2023 09:39:59 +0300 Subject: [PATCH 6/9] Fix tests --- tests/Unit/Helpers/DefinitionResolverTest.php | 2 +- tests/Unit/Helpers/DefinitionValidatorTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Helpers/DefinitionResolverTest.php b/tests/Unit/Helpers/DefinitionResolverTest.php index 3b07274..9215ba7 100644 --- a/tests/Unit/Helpers/DefinitionResolverTest.php +++ b/tests/Unit/Helpers/DefinitionResolverTest.php @@ -45,7 +45,7 @@ public function testEnsureResolvableDefinition(): void $this->expectException(InvalidConfigException::class); $this->expectExceptionMessage( 'Only references are allowed in constructor arguments, a definition object was provided: ' . - ValueDefinition::class + var_export(new ValueDefinition(7), true), ); DefinitionResolver::ensureResolvable(new ValueDefinition(7)); } diff --git a/tests/Unit/Helpers/DefinitionValidatorTest.php b/tests/Unit/Helpers/DefinitionValidatorTest.php index 72c665f..4aa40da 100644 --- a/tests/Unit/Helpers/DefinitionValidatorTest.php +++ b/tests/Unit/Helpers/DefinitionValidatorTest.php @@ -311,7 +311,7 @@ public function testDefinitionInArguments(): void $this->expectException(InvalidConfigException::class); $this->expectExceptionMessage( 'Only references are allowed in constructor arguments, a definition object was provided: ' . - ValueDefinition::class + var_export(new ValueDefinition(56), true), ); DefinitionValidator::validate([ 'class' => GearBox::class, From ae934721d9e2d1ccc85b22d27790c655e669387a Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 19 Aug 2023 11:52:16 +0300 Subject: [PATCH 7/9] Fix PHP 8.0 support --- src/ServiceDefinition.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ServiceDefinition.php b/src/ServiceDefinition.php index 58cf373..3a561db 100644 --- a/src/ServiceDefinition.php +++ b/src/ServiceDefinition.php @@ -58,11 +58,10 @@ public function sets(array $properties): self public function resolve(ContainerInterface $container): mixed { - $config = [ + $config = array_merge($this->calls, [ ArrayDefinition::CLASS_NAME => $this->class, ArrayDefinition::CONSTRUCTOR => $this->constructor, - ...$this->calls, - ]; + ]); return ArrayDefinition::fromConfig($config)->resolve($container); } From 0beaf82840199cf8b2f64082d95eada1a26fcb4f Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 19 Aug 2023 12:00:43 +0300 Subject: [PATCH 8/9] Fix psalm errors --- src/ServiceDefinition.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ServiceDefinition.php b/src/ServiceDefinition.php index 3a561db..abbeaf2 100644 --- a/src/ServiceDefinition.php +++ b/src/ServiceDefinition.php @@ -9,14 +9,23 @@ final class ServiceDefinition implements DefinitionInterface { + /** + * @psalm-var array + */ private array $calls = []; private function __construct( + /** + * @psalm-var class-string + */ private string $class, private array $constructor = [], ) { } + /** + * @psalm-param class-string $class + */ public static function for(string $class, array $constructor = []): self { return new self($class, $constructor); @@ -34,6 +43,9 @@ public function call(string $method, array $arguments = []): self return $this; } + /** + * @psalm-param array $methods + */ public function calls(array $methods): self { foreach ($methods as $method => $arguments) { @@ -48,6 +60,9 @@ public function set(string $property, mixed $value): self return $this; } + /** + * @psalm-param array $properties + */ public function sets(array $properties): self { foreach ($properties as $property => $value) { @@ -64,9 +79,4 @@ public function resolve(ContainerInterface $container): mixed ]); return ArrayDefinition::fromConfig($config)->resolve($container); } - - public function merge(self $other) - { - // TBD - } } From e1cdb1601f964f113f38256fe9a36bf60ced1522 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sat, 19 Aug 2023 12:19:25 +0300 Subject: [PATCH 9/9] Add merge functionality --- src/ServiceDefinition.php | 39 ++++++++++++++++++++++ tests/Unit/ServiceDefinitionTest.php | 49 +++++----------------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/ServiceDefinition.php b/src/ServiceDefinition.php index abbeaf2..f6d607c 100644 --- a/src/ServiceDefinition.php +++ b/src/ServiceDefinition.php @@ -6,6 +6,7 @@ use Psr\Container\ContainerInterface; use Yiisoft\Definitions\Contract\DefinitionInterface; +use Yiisoft\Definitions\Helpers\ArrayDefinitionHelper; final class ServiceDefinition implements DefinitionInterface { @@ -79,4 +80,42 @@ public function resolve(ContainerInterface $container): mixed ]); return ArrayDefinition::fromConfig($config)->resolve($container); } + + public function merge(self $other): self + { + $new = clone $this; + $new->class = $other->class; + $new->constructor = ArrayDefinitionHelper::mergeArguments($this->constructor, $other->constructor); + + $calls = $this->calls; + foreach ($other->calls as $key => $item) { + if (str_starts_with($key, '$')) { + $calls[$key] = $item; + } elseif (str_ends_with($key, '()')) { + /** @psalm-suppress MixedArgument */ + $arguments = isset($calls[$key]) + ? ArrayDefinitionHelper::mergeArguments($calls[$key], $item) + : $item; + $calls[$key] = $arguments; + } + } + $new->calls = $calls; + + return $new; + } + + public function getClass(): string + { + return $this->class; + } + + public function getConstructor(): array + { + return $this->constructor; + } + + public function getCalls(): array + { + return $this->calls; + } } diff --git a/tests/Unit/ServiceDefinitionTest.php b/tests/Unit/ServiceDefinitionTest.php index 0975987..4133ac7 100644 --- a/tests/Unit/ServiceDefinitionTest.php +++ b/tests/Unit/ServiceDefinitionTest.php @@ -6,13 +6,9 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; -use Yiisoft\Definitions\ArrayDefinition; use Yiisoft\Definitions\Exception\InvalidConfigException; use Yiisoft\Definitions\Reference; use Yiisoft\Definitions\ServiceDefinition; -use Yiisoft\Definitions\Tests\Support\Car; -use Yiisoft\Definitions\Tests\Support\ColorInterface; -use Yiisoft\Definitions\Tests\Support\ColorPink; use Yiisoft\Definitions\Tests\Support\EngineInterface; use Yiisoft\Definitions\Tests\Support\EngineMarkOne; use Yiisoft\Definitions\Tests\Support\EngineMarkTwo; @@ -415,15 +411,13 @@ public function testMethodWithWrongReferenceVariadicArgument(): void public function testMerge(): void { - $this->markTestSkipped('TBD'); - $a = ServiceDefinition::for(Phone::class) - ->constructor(['version' => '2.0']) + ->constructor(['name' => 'Retro', 'version' => '1.0']) ->set('codeName', 'a') - ->call('setColors', ['red', 'green'], ); + ->call('setColors', ['red', 'green']); $b = ServiceDefinition::for(Phone::class) - ->constructor(['name' => 'Retro', 'version' => '1.0']) + ->constructor(['version' => '2.0']) ->set('dev', true) ->set('codeName', 'b') ->call('setId', [42]) @@ -432,21 +426,20 @@ public function testMerge(): void $c = $a->merge($b); $this->assertSame(Phone::class, $c->getClass()); - $this->assertSame(['name' => 'Retro', 'version' => '2.0'], $c->getConstructorArguments()); + $this->assertSame(['name' => 'Retro', 'version' => '2.0'], $c->getConstructor()); $this->assertSame( [ - 'codeName' => [ArrayDefinition::TYPE_PROPERTY, 'codeName', 'b'], - 'setColors' => [ArrayDefinition::TYPE_METHOD, 'setColors', ['yellow', 'green']], - 'dev' => [ArrayDefinition::TYPE_PROPERTY, 'dev', true], - 'setId' => [ArrayDefinition::TYPE_METHOD, 'setId', [42]], + '$codeName' => 'b', + 'setColors()' => ['yellow', 'green'], + '$dev' => true, + 'setId()' => [42], ], - $c->getMethodsAndProperties(), + $c->getCalls(), ); } public function testMergeImmutability(): void { - $this->markTestSkipped('TBD'); $a = ServiceDefinition::for(Phone::class); $b = ServiceDefinition::for(Phone::class); $c = $a->merge($b); @@ -467,30 +460,6 @@ public function testArgumentsIndexedBothByNameAndByPosition(): void $definition->resolve($container); } - public function testReferenceContainer(): void - { - $this->markTestSkipped('TBD'); - $container = new SimpleContainer([ - EngineInterface::class => new EngineMarkOne(), - ]); - $referenceContainer = new SimpleContainer([ - ColorInterface::class => new ColorPink(), - EngineInterface::class => new EngineMarkTwo(), - ]); - - $definition = ServiceDefinition::for(Car::class) - ->call('setColor', [Reference::to(ColorInterface::class)]); - - $newDefinition = $definition->withReferenceContainer($referenceContainer); - - /** @var Car $object */ - $object = $newDefinition->resolve($container); - - $this->assertNotSame($definition, $newDefinition); - $this->assertInstanceOf(Car::class, $object); - $this->assertInstanceOf(EngineMarkOne::class, $object->getEngine()); - } - public function testMagicMethods(): void { $definiton = ServiceDefinition::for(Recorder::class)