From 89d8ba0ec866d3f0eeee3614976596895579b1e3 Mon Sep 17 00:00:00 2001 From: Zacharias Luiten Date: Tue, 17 Aug 2021 15:57:25 +0200 Subject: [PATCH] Support both positional and named parameters --- docs/basic/bean-parameters.md | 28 +++++++-- src/bitExpert/Disco/AnnotationBeanFactory.php | 5 +- src/bitExpert/Disco/Annotations/Parameter.php | 20 +++---- .../Disco/BeanFactoryConfiguration.php | 5 +- .../ParameterAwareMethodGenerator.php | 32 +++++++++-- .../Disco/AnnotationBeanFactoryUnitTest.php | 57 +++++++++++++++++-- .../Disco/Annotations/ParameterUnitTest.php | 16 +++--- .../BeanConfigurationWithParameters.php | 47 ++++++++++++--- ...ostProcessorAndParameterizedDependency.php | 8 ++- .../bitExpert/Disco/Helper/SampleService.php | 11 ++++ .../ConfigurationGeneratorUnitTest.php | 10 +--- 11 files changed, 183 insertions(+), 56 deletions(-) diff --git a/docs/basic/bean-parameters.md b/docs/basic/bean-parameters.md index 1b7ed00..cca45fe 100644 --- a/docs/basic/bean-parameters.md +++ b/docs/basic/bean-parameters.md @@ -2,10 +2,16 @@ Bean instances can be parameterized by a given configuration. To access the configuration add a `#[Parameter]` attribute to your Bean method. -The `#[Parameter(name: 'paramName', key: 'config.key')]` attribute requires at least the `param` (which must match a param name of the Bean method) and the `key` which will be used to look for in the configuration array. +Configuration parameters can be passed to the Bean configuration method as [named](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments) +or as positional arguments. A `Parameter` will be used as a named argument when the `name` argument is set. -In the following example the value of configuration key `configKey` gets passed to the bean configuation for the argument named `$test`. -Configuration parameters are passed to the bean configuration method using [named arguments](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments): +So `#[Parameter(name: 'argName', key: 'config.key)]` is a named parameter whereas `#[Parameter(key: 'config.key')]` is a positional parameter. +As one might expect, the order of the positional parameters matter. Therefore, the recommended way of configuring parameters is using named parameters. +Named and positional parameters can be mixed. + +The `#[Parameter]` attribute requires at least `key` which will be used to look for in the configuration array. + +In the following example the value of configuration key `configKey` gets passed to the Bean configuration for the argument named `$test`. ```php setTest($test); return $service; } + + #[Bean] + #[Parameter(name: 'anotherTest', key: 'configKey2')] + #[Parameter(key: 'configKey1')] + public function mySampleService(string $test = '', string $anotherTest = '') : SampleService + { + $service = new SampleService(); + $service->setTest($test); + $service->setAnotherTest($anotherTest); + return $service; + } } ``` @@ -34,7 +51,7 @@ The configuration array gets passed to the `\bitExpert\Disco\AnnotationBeanFacto ```php 'This is a test.']; +$parameters = ['configKey1' => 'This is a test.' 'configKey2' => 'This is another test.']; $beanFactory = new \bitExpert\Disco\AnnotationBeanFactory( MyConfiguration::class, @@ -45,6 +62,7 @@ $beanFactory = new \bitExpert\Disco\AnnotationBeanFactory( $sampleService = $beanFactory->get('mySampleService'); echo $sampleService->test; // Output: This is a test. +echo $sampleService->anotherTest; // Output: This is another test. ``` ## Default Parameter Values diff --git a/src/bitExpert/Disco/AnnotationBeanFactory.php b/src/bitExpert/Disco/AnnotationBeanFactory.php index 9d30ec3..f569244 100644 --- a/src/bitExpert/Disco/AnnotationBeanFactory.php +++ b/src/bitExpert/Disco/AnnotationBeanFactory.php @@ -32,14 +32,13 @@ class AnnotationBeanFactory implements BeanFactory * * @param class-string $configClassName * @param array $parameters - * @param BeanFactoryConfiguration $config + * @param BeanFactoryConfiguration|null $config */ public function __construct( string $configClassName, array $parameters = [], BeanFactoryConfiguration $config = null - ) - { + ) { if ($config === null) { $config = new BeanFactoryConfiguration(sys_get_temp_dir()); } diff --git a/src/bitExpert/Disco/Annotations/Parameter.php b/src/bitExpert/Disco/Annotations/Parameter.php index 94d4185..d7affac 100644 --- a/src/bitExpert/Disco/Annotations/Parameter.php +++ b/src/bitExpert/Disco/Annotations/Parameter.php @@ -24,37 +24,37 @@ #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] final class Parameter { - private string $name; - private string $key; + private ?string $name; + private mixed $defaultValue; private bool $required; /** - * @param string $name * @param string $key * @param bool $required - * @param mixed|null $default + * @param mixed $default + * @param string|null $name */ - public function __construct(string $name, string $key, bool $required = true, mixed $default = null) + public function __construct(string $key, bool $required = true, mixed $default = null, ?string $name = null) { - Assert::minLength($name, 1); Assert::minLength($key, 1); + Assert::nullOrMinLength($name, 1); - $this->name = $name; $this->key = $key; + $this->name = $name; $this->defaultValue = $default; $this->required = $required; } /** - * Return the name of the parameter + * Return the name of the argument or null in case of a positioned argument * - * @return string + * @return string|null */ - public function getName(): string + public function getName(): ?string { return $this->name; } diff --git a/src/bitExpert/Disco/BeanFactoryConfiguration.php b/src/bitExpert/Disco/BeanFactoryConfiguration.php index 82086ee..bed2202 100644 --- a/src/bitExpert/Disco/BeanFactoryConfiguration.php +++ b/src/bitExpert/Disco/BeanFactoryConfiguration.php @@ -144,10 +144,7 @@ public function getProxyManagerConfiguration(): Configuration { $proxyManagerConfiguration = new Configuration(); $proxyManagerConfiguration->setProxiesTargetDir($this->proxyTargetDir); - - if ($this->proxyWriterGenerator instanceof GeneratorStrategyInterface) { - $proxyManagerConfiguration->setGeneratorStrategy($this->proxyWriterGenerator); - } + $proxyManagerConfiguration->setGeneratorStrategy($this->proxyWriterGenerator); if ($this->proxyAutoloader instanceof AutoloaderInterface) { $proxyManagerConfiguration->setProxyAutoloader($this->proxyAutoloader); diff --git a/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/ParameterAwareMethodGenerator.php b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/ParameterAwareMethodGenerator.php index 0d45fab..26c61b0 100644 --- a/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/ParameterAwareMethodGenerator.php +++ b/src/bitExpert/Disco/Proxy/Configuration/MethodGenerator/ParameterAwareMethodGenerator.php @@ -32,9 +32,10 @@ protected static function convertMethodParamsToString( array $methodParameters, GetParameter $parameterValuesMethod ): string { - $parameters = []; + $positionalArgs = []; + $namedArgs = []; + foreach ($methodParameters as $methodParameter) { - /** @var Parameter $methodParameter */ $defaultValue = $methodParameter->getDefaultValue(); switch (\gettype($defaultValue)) { case 'string': @@ -50,12 +51,33 @@ protected static function convertMethodParamsToString( break; } - $template = ($defaultValue === '') ? '$this->%s("%s", %s)' : '$this->%s("%s", %s, %s)'; $required = $methodParameter->isRequired() ? 'true' : 'false'; $methodName = $parameterValuesMethod->getName(); - $parameters[] = \sprintf($template, $methodName, $methodParameter->getKey(), $required, $defaultValue); + $argName = $methodParameter->getName(); + + if (null === $argName) { + $template = ($defaultValue === '') ? '$this->%s("%s", %s)' : '$this->%s("%s", %s, %s)'; + $positionalArgs[] = \sprintf( + $template, + $methodName, + $methodParameter->getKey(), + $required, + $defaultValue + ); + } else { + $template = ($defaultValue === '') ? '%s: $this->%s("%s", %s)' : '%s: $this->%s("%s", %s, %s)'; + $namedArgs[] = \sprintf( + $template, + $argName, + $methodName, + $methodParameter->getKey(), + $required, + $defaultValue + ); + } + } - return \implode(', ', $parameters); + return \implode(', ', [...$positionalArgs, ...$namedArgs]); } } diff --git a/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php b/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php index 04f02cb..7853378 100644 --- a/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php +++ b/tests/bitExpert/Disco/AnnotationBeanFactoryUnitTest.php @@ -260,14 +260,15 @@ public function beanFactoryPostProcessorCanBeConfiguredWithParameterizedDependen { $this->beanFactory = new AnnotationBeanFactory( BeanConfigurationWithPostProcessorAndParameterizedDependency::class, - ['test' => 'injectedValue'] + ['configKey1' => 'injectedValue1', 'configKey2' => 'injectedValue2'] ); BeanFactoryRegistry::register($this->beanFactory); /** @var SampleService $bean */ $bean = $this->beanFactory->get('nonSingletonNonLazyRequestBean'); self::assertInstanceOf(stdClass::class, $bean->test); - self::assertEquals('injectedValue', $bean->test->property); + self::assertEquals('injectedValue1', $bean->test->property1); + self::assertEquals('injectedValue2', $bean->test->property2); } /** @@ -277,7 +278,7 @@ public function parameterPassedToBeanFactoryGetsInjectedInBean(): void { $this->beanFactory = new AnnotationBeanFactory( BeanConfigurationWithParameters::class, - ['test' => 'injectedValue'] + ['configKey' => 'injectedValue'] ); BeanFactoryRegistry::register($this->beanFactory); @@ -285,6 +286,54 @@ public function parameterPassedToBeanFactoryGetsInjectedInBean(): void self::assertEquals('injectedValue', $bean->test); } + /** + * @test + */ + public function parametersPassedToBeanFactoryGetsInjectedInBeanWithPositionalParams(): void + { + $this->beanFactory = new AnnotationBeanFactory( + BeanConfigurationWithParameters::class, + ['configKey1' => 'injectedValue1', 'configKey2' => 'injectedValue2'] + ); + BeanFactoryRegistry::register($this->beanFactory); + + $bean = $this->beanFactory->get('sampleServiceWithPositionalParams'); + self::assertEquals('injectedValue1', $bean->test); + self::assertEquals('injectedValue2', $bean->anotherTest); + } + + /** + * @test + */ + public function parametersPassedToBeanFactoryGetsInjectedInBeanWithNamedParams(): void + { + $this->beanFactory = new AnnotationBeanFactory( + BeanConfigurationWithParameters::class, + ['configKey1' => 'injectedValue1', 'configKey2' => 'injectedValue2'] + ); + BeanFactoryRegistry::register($this->beanFactory); + + $bean = $this->beanFactory->get('sampleServiceWithNamedParams'); + self::assertEquals('injectedValue1', $bean->test); + self::assertEquals('injectedValue2', $bean->anotherTest); + } + + /** + * @test + */ + public function parametersPassedToBeanFactoryGetsInjectedInBeanWithMixedPositionalAndNamedParams(): void + { + $this->beanFactory = new AnnotationBeanFactory( + BeanConfigurationWithParameters::class, + ['configKey1' => 'injectedValue1', 'configKey2' => 'injectedValue2'] + ); + BeanFactoryRegistry::register($this->beanFactory); + + $bean = $this->beanFactory->get('sampleServiceWithMixedPositionalAndNamedParams'); + self::assertEquals('injectedValue1', $bean->test); + self::assertEquals('injectedValue2', $bean->anotherTest); + } + /** * @test */ @@ -293,7 +342,7 @@ public function nestedParameterKeyPassedToBeanFactoryGetsInjectedInBean(): void $this->beanFactory = new AnnotationBeanFactory( BeanConfigurationWithParameters::class, [ - 'test' => [ + 'config' => [ 'nested' => [ 'key' => 'injectedValue' ] diff --git a/tests/bitExpert/Disco/Annotations/ParameterUnitTest.php b/tests/bitExpert/Disco/Annotations/ParameterUnitTest.php index 30f9180..b1e1975 100644 --- a/tests/bitExpert/Disco/Annotations/ParameterUnitTest.php +++ b/tests/bitExpert/Disco/Annotations/ParameterUnitTest.php @@ -28,7 +28,7 @@ public function emptyNameWillThrowAnnotationException(): void { $this->expectException(InvalidArgumentException::class); - new Parameter('', 'myParam'); + new Parameter(key: 'myParam', name: ''); } /** @@ -38,7 +38,7 @@ public function emptyKeyWillThrowAnnotationException(): void { $this->expectException(InvalidArgumentException::class); - new Parameter('name', ''); + new Parameter(key: ''); } /** @@ -46,9 +46,9 @@ public function emptyKeyWillThrowAnnotationException(): void */ public function nameIsSet(): void { - $parameter = new Parameter(name: 'paramName', key: 'key'); + $parameter = new Parameter(name: 'argName', key: 'key'); - self::assertSame('paramName', $parameter->getName()); + self::assertSame('argName', $parameter->getName()); } /** @@ -56,7 +56,7 @@ public function nameIsSet(): void */ public function keyIsSet(): void { - $parameter = new Parameter(name: 'paramName', key: 'key'); + $parameter = new Parameter(name: 'argName', key: 'key'); self::assertSame('key', $parameter->getKey()); } @@ -78,7 +78,7 @@ public function defaultValueDefaultsToNull(): void */ public function defaultValueIsParsed(mixed $defaultValue): void { - $parameter = new Parameter(name: 'paramName', key: 'myParam', default: $defaultValue); + $parameter = new Parameter(name: 'argName', key: 'myParam', default: $defaultValue); self::assertSame($defaultValue, $parameter->getDefaultValue()); } @@ -88,7 +88,7 @@ public function defaultValueIsParsed(mixed $defaultValue): void */ public function requireDefaultsToTrue(): void { - $parameter = new Parameter(name: 'paramName', key: 'myParam'); + $parameter = new Parameter(name: 'argName', key: 'myParam'); self::assertTrue($parameter->isRequired()); } @@ -100,7 +100,7 @@ public function requireDefaultsToTrue(): void */ public function requireIsParsed(bool $requireValue): void { - $parameter = new Parameter(name: 'paramName', key: 'myParam', required: $requireValue); + $parameter = new Parameter(name: 'argName', key: 'myParam', required: $requireValue); self::assertSame($requireValue, $parameter->isRequired()); } diff --git a/tests/bitExpert/Disco/Config/BeanConfigurationWithParameters.php b/tests/bitExpert/Disco/Config/BeanConfigurationWithParameters.php index 74478db..f0c5d08 100644 --- a/tests/bitExpert/Disco/Config/BeanConfigurationWithParameters.php +++ b/tests/bitExpert/Disco/Config/BeanConfigurationWithParameters.php @@ -21,7 +21,7 @@ class BeanConfigurationWithParameters { #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test')] + #[Parameter(name: 'test', key: 'configKey')] public function sampleServiceWithParam($test = ''): SampleService { $service = new SampleService(); @@ -30,7 +30,40 @@ public function sampleServiceWithParam($test = ''): SampleService } #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test', default: null)] + #[Parameter('configKey1')] + #[Parameter('configKey2')] + public function sampleServiceWithPositionalParams($test = '', $anotherTest = ''): SampleService + { + $service = new SampleService(); + $service->setTest($test); + $service->setAnotherTest($anotherTest); + return $service; + } + + #[Bean(singleton: false)] + #[Parameter(key: 'configKey2', name: 'anotherTest')] + #[Parameter(key: 'configKey1', name: 'test')] + public function sampleServiceWithNamedParams($test = '', $anotherTest = ''): SampleService + { + $service = new SampleService(); + $service->setTest($test); + $service->setAnotherTest($anotherTest); + return $service; + } + + #[Bean(singleton: false)] + #[Parameter(key: 'configKey2', name: 'anotherTest')] + #[Parameter(key: 'configKey1')] + public function sampleServiceWithMixedPositionalAndNamedParams($test = '', $anotherTest = ''): SampleService + { + $service = new SampleService(); + $service->setTest($test); + $service->setAnotherTest($anotherTest); + return $service; + } + + #[Bean(singleton: false)] + #[Parameter(name: 'test', key: 'configKey', default: null)] public function sampleServiceWithParamNull($test = ''): SampleService { $service = new SampleService(); @@ -39,7 +72,7 @@ public function sampleServiceWithParamNull($test = ''): SampleService } #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test', default: true)] + #[Parameter(name: 'test', key: 'configKey', default: true)] public function sampleServiceWithParamBool($test = ''): SampleService { $service = new SampleService(); @@ -48,7 +81,7 @@ public function sampleServiceWithParamBool($test = ''): SampleService } #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test', default: 0)] + #[Parameter(name: 'test', key: 'configKey', default: 0)] public function sampleServiceWithParamEmpty($test = ''): SampleService { $service = new SampleService(); @@ -57,7 +90,7 @@ public function sampleServiceWithParamEmpty($test = ''): SampleService } #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test.nested.key')] + #[Parameter(name: 'test', key: 'config.nested.key')] public function sampleServiceWithNestedParamKey($test = ''): SampleService { $service = new SampleService(); @@ -66,7 +99,7 @@ public function sampleServiceWithNestedParamKey($test = ''): SampleService } #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test', default: 'myDefaultValue')] + #[Parameter(name: 'test', key: 'configKey', default: 'myDefaultValue')] public function sampleServiceWithParamDefaultValue($test = ''): SampleService { $service = new SampleService(); @@ -75,7 +108,7 @@ public function sampleServiceWithParamDefaultValue($test = ''): SampleService } #[Bean(singleton: false)] - #[Parameter(name: 'test', key: 'test', required: false)] + #[Parameter(name: 'test', key: 'configKey', required: false)] public function sampleServiceWithoutRequiredParam($test = ''): SampleService { $service = new SampleService(); diff --git a/tests/bitExpert/Disco/Config/BeanConfigurationWithPostProcessorAndParameterizedDependency.php b/tests/bitExpert/Disco/Config/BeanConfigurationWithPostProcessorAndParameterizedDependency.php index eda3687..27a5b4f 100644 --- a/tests/bitExpert/Disco/Config/BeanConfigurationWithPostProcessorAndParameterizedDependency.php +++ b/tests/bitExpert/Disco/Config/BeanConfigurationWithPostProcessorAndParameterizedDependency.php @@ -29,11 +29,13 @@ public function sampleServiceBeanPostProcessor(): ParameterizedSampleServiceBean } #[Bean] - #[Parameter(name: 'test', key: 'test')] - public function dependency($test = ''): \stdClass + #[Parameter(name: 'property1', key: 'configKey1')] + #[Parameter(name: 'property2', key: 'configKey2')] + public function dependency(string $property1 = '', string $property2 = ''): \stdClass { $object = new \stdClass(); - $object->property = $test; + $object->property1 = $property1; + $object->property2 = $property2; return $object; } diff --git a/tests/bitExpert/Disco/Helper/SampleService.php b/tests/bitExpert/Disco/Helper/SampleService.php index 23ef031..87a9239 100644 --- a/tests/bitExpert/Disco/Helper/SampleService.php +++ b/tests/bitExpert/Disco/Helper/SampleService.php @@ -15,6 +15,7 @@ class SampleService implements SampleServiceInterface { public $test; + public $anotherTest; /** * Setter method for the $test property. @@ -25,4 +26,14 @@ public function setTest($test) { $this->test = $test; } + + /** + * Setter method for the $anotherTest property. + * + * @param mixed $test + */ + public function setAnotherTest($test) + { + $this->anotherTest = $test; + } } diff --git a/tests/bitExpert/Disco/Proxy/Configuration/ConfigurationGeneratorUnitTest.php b/tests/bitExpert/Disco/Proxy/Configuration/ConfigurationGeneratorUnitTest.php index b701110..5d6d64e 100644 --- a/tests/bitExpert/Disco/Proxy/Configuration/ConfigurationGeneratorUnitTest.php +++ b/tests/bitExpert/Disco/Proxy/Configuration/ConfigurationGeneratorUnitTest.php @@ -21,6 +21,7 @@ use bitExpert\Disco\Config\MissingBeanAnnotationConfiguration; use bitExpert\Disco\Config\MissingReturnTypeConfiguration; use bitExpert\Disco\Config\NonExistentReturnTypeConfiguration; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ProxyManager\Exception\InvalidProxiedClassException; use Laminas\Code\Generator\ClassGenerator; @@ -36,7 +37,7 @@ class ConfigurationGeneratorUnitTest extends TestCase private $configGenerator; /** - * @var ClassGenerator&\PHPUnit\Framework\MockObject\MockObject + * @var ClassGenerator&MockObject */ private $classGenerator; @@ -48,9 +49,7 @@ public function setUp(): void parent::setUp(); $this->configGenerator = new ConfigurationGenerator(); - /** @var ClassGenerator&\PHPUnit\Framework\MockObject\MockObject $mock */ - $mock = $this->createMock(ClassGenerator::class); - $this->classGenerator = $mock; + $this->classGenerator = $this->createMock(ClassGenerator::class); } /** @@ -127,9 +126,6 @@ public function missingConfigurationAttributeThrowsException(): void $this->expectException(InvalidProxiedClassException::class); $this->expectExceptionMessageMatches('/#\[Configuration\] attribute missing!/'); - /** - * @foo - */ $configObject = new class { public function foo(): string