diff --git a/fixtures/Entity/DummyWithMethods.php b/fixtures/Entity/DummyWithMethods.php new file mode 100644 index 000000000..dff606c2f --- /dev/null +++ b/fixtures/Entity/DummyWithMethods.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Entity; + +class DummyWithMethods +{ + private $foo1; + private $foo2; + private $bar1; + private $bar2; + private $baz1; + private $baz2; + private $baz3; + + public function __construct(string $foo1, string $foo2) + { + $this->foo1 = $foo1; + $this->foo2 = $foo2; + } + + public static function create(string $foo1, string $foo2) + { + return new self($foo1, $foo2); + } + + public function bar(string $bar1, string $bar2) + { + $this->bar1 = $bar1; + $this->bar2 = $bar2; + } + + public function methodWithVariadic(string $baz1, string $baz2, array ...$baz3) + { + $this->baz1 = $baz1; + $this->baz2 = $baz2; + $this->baz3 = $baz3; + } + + public function methodWithDefaultValues(string $baz1 = 'value 1', string $baz2 = 'value 2', string $baz3 = 'value 3') + { + $this->baz1 = $baz1; + $this->baz2 = $baz2; + $this->baz3 = $baz3; + } + + public function methodWithNullables(?string $bar1, ?string $bar2) + { + $this->bar1 = $bar1; + $this->bar2 = $bar2; + } +} diff --git a/fixtures/Parser/files/json/named_parameters.json b/fixtures/Parser/files/json/named_parameters.json new file mode 100644 index 000000000..e77022f16 --- /dev/null +++ b/fixtures/Parser/files/json/named_parameters.json @@ -0,0 +1,18 @@ +{ + "Nelmio\\Alice\\DummyWithMethods": { + "dummy_with_methods": { + "__construct": { + "$foo1": "foo1", + "$foo2": "foo2" + }, + "__calls": [ + { + "bar": { + "$bar1": "bar1", + "$bar2": "bar2" + } + } + ] + } + } +} diff --git a/fixtures/Parser/files/php/named_parameters.php b/fixtures/Parser/files/php/named_parameters.php new file mode 100644 index 000000000..aa4cbe403 --- /dev/null +++ b/fixtures/Parser/files/php/named_parameters.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +return [ + 'Nelmio\Alice\DummyWithMethods' =>[ + 'dummy_with_methods' =>[ + '__construct' =>[ + '$foo1' =>'foo1', + '$foo2' =>'foo2' + ], + '__calls' =>[ + [ + 'bar' =>[ + '$bar1' =>'bar1', + '$bar2' =>'bar2' + ] + ] + ] + ] + ] +]; diff --git a/fixtures/Parser/files/yaml/named_parameters.yml b/fixtures/Parser/files/yaml/named_parameters.yml new file mode 100644 index 000000000..67b27ba1f --- /dev/null +++ b/fixtures/Parser/files/yaml/named_parameters.yml @@ -0,0 +1,9 @@ +Nelmio\Alice\DummyWithMethods: + dummy_with_methods: + __construct: + $foo1: foo1 + $foo2: foo2 + __calls: + - bar: + $bar1: bar1 + $bar2: bar2 diff --git a/src/Bridge/Symfony/Resources/config/generator/caller.xml b/src/Bridge/Symfony/Resources/config/generator/caller.xml index cb26021fb..d1e64967f 100644 --- a/src/Bridge/Symfony/Resources/config/generator/caller.xml +++ b/src/Bridge/Symfony/Resources/config/generator/caller.xml @@ -20,6 +20,7 @@ class="Nelmio\Alice\Generator\Caller\SimpleCaller"> + + @@ -50,6 +51,7 @@ + diff --git a/src/Bridge/Symfony/Resources/config/generator/resolver/named_arguments.xml b/src/Bridge/Symfony/Resources/config/generator/resolver/named_arguments.xml new file mode 100644 index 000000000..cdf239d21 --- /dev/null +++ b/src/Bridge/Symfony/Resources/config/generator/resolver/named_arguments.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + diff --git a/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php b/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php index db867d6d0..5e90bc102 100644 --- a/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php +++ b/src/Generator/Caller/Chainable/SimpleMethodCallProcessor.php @@ -42,7 +42,7 @@ public function process( GenerationContext $context, MethodCallInterface $methodCall ): ResolvedFixtureSet { - $result = $object->getInstance()->{$methodCall->getMethod()}(...$methodCall->getArguments()); + $result = $object->getInstance()->{$methodCall->getMethod()}(...array_values($methodCall->getArguments())); if ($context->needsCallResult()) { $object = $object->withInstance($result); diff --git a/src/Generator/Caller/SimpleCaller.php b/src/Generator/Caller/SimpleCaller.php index bf6e3ff49..f0671ff76 100644 --- a/src/Generator/Caller/SimpleCaller.php +++ b/src/Generator/Caller/SimpleCaller.php @@ -18,6 +18,7 @@ use Nelmio\Alice\FixtureInterface; use Nelmio\Alice\Generator\CallerInterface; use Nelmio\Alice\Generator\GenerationContext; +use Nelmio\Alice\Generator\NamedArgumentsResolver; use Nelmio\Alice\Generator\ResolvedFixtureSet; use Nelmio\Alice\Generator\ValueResolverAwareInterface; use Nelmio\Alice\Generator\ValueResolverInterface; @@ -41,10 +42,20 @@ final class SimpleCaller implements CallerInterface, ValueResolverAwareInterface */ private $resolver; - public function __construct(CallProcessorInterface $callProcessor, ValueResolverInterface $resolver = null) - { + /** + * @var NamedArgumentsResolver|null + */ + private $namedArgumentsResolver; + + // TODO: make $namedArgumentsResolver non-nullable in 4.0. It is currently nullable only for BC purposes + public function __construct( + CallProcessorInterface $callProcessor, + ValueResolverInterface $resolver = null, + NamedArgumentsResolver $namedArgumentsResolver = null + ) { $this->callProcessor = $callProcessor; $this->resolver = $resolver; + $this->namedArgumentsResolver = $namedArgumentsResolver; } /** @@ -52,7 +63,7 @@ public function __construct(CallProcessorInterface $callProcessor, ValueResolver */ public function withValueResolver(ValueResolverInterface $resolver): self { - return new self($this->callProcessor, $resolver); + return new self($this->callProcessor, $resolver, $this->namedArgumentsResolver); } /** @@ -108,6 +119,10 @@ private function processArguments( } } + if (null !== $this->namedArgumentsResolver) { + $arguments = $this->namedArgumentsResolver->resolveArguments($arguments, $fixture->getClassName(), $methodCall->getMethod()); + } + return [$methodCall->withArguments($arguments), $fixtureSet]; } } diff --git a/src/Generator/Instantiator/Chainable/NoCallerMethodCallInstantiator.php b/src/Generator/Instantiator/Chainable/NoCallerMethodCallInstantiator.php index d22a8eeb1..2eb63d525 100644 --- a/src/Generator/Instantiator/Chainable/NoCallerMethodCallInstantiator.php +++ b/src/Generator/Instantiator/Chainable/NoCallerMethodCallInstantiator.php @@ -15,9 +15,21 @@ use Nelmio\Alice\Definition\MethodCall\NoMethodCall; use Nelmio\Alice\FixtureInterface; +use Nelmio\Alice\Generator\NamedArgumentsResolver; final class NoCallerMethodCallInstantiator extends AbstractChainableInstantiator { + /** + * @var NamedArgumentsResolver|null + */ + private $namedArgumentsResolver; + + // TODO: make $namedArgumentsResolver non-nullable in 4.0. It is currently nullable only for BC purposes + public function __construct(NamedArgumentsResolver $namedArgumentsResolver = null) + { + $this->namedArgumentsResolver = $namedArgumentsResolver; + } + /** * @inheritDoc */ @@ -35,9 +47,13 @@ protected function createInstance(FixtureInterface $fixture) { list($class, $arguments) = [ $fixture->getClassName(), - array_values($fixture->getSpecs()->getConstructor()->getArguments()) + $fixture->getSpecs()->getConstructor()->getArguments() ]; - return new $class(...$arguments); + if (null !== $this->namedArgumentsResolver) { + $arguments = $this->namedArgumentsResolver->resolveArguments($arguments, $class, '__construct'); + } + + return new $class(...array_values($arguments)); } } diff --git a/src/Generator/Instantiator/Chainable/StaticFactoryInstantiator.php b/src/Generator/Instantiator/Chainable/StaticFactoryInstantiator.php index 11bcbd7ab..e4bdf68eb 100644 --- a/src/Generator/Instantiator/Chainable/StaticFactoryInstantiator.php +++ b/src/Generator/Instantiator/Chainable/StaticFactoryInstantiator.php @@ -16,10 +16,22 @@ use Nelmio\Alice\Definition\MethodCall\NoMethodCall; use Nelmio\Alice\Definition\ServiceReference\StaticReference; use Nelmio\Alice\FixtureInterface; +use Nelmio\Alice\Generator\NamedArgumentsResolver; use Nelmio\Alice\Throwable\Exception\Generator\Instantiator\InstantiationExceptionFactory; final class StaticFactoryInstantiator extends AbstractChainableInstantiator { + /** + * @var NamedArgumentsResolver|null + */ + private $namedArgumentsResolver; + + // TODO: make $namedArgumentsResolver non-nullable in 4.0. It is currently nullable only for BC purposes + public function __construct(NamedArgumentsResolver $namedArgumentsResolver = null) + { + $this->namedArgumentsResolver = $namedArgumentsResolver; + } + /** * @inheritDoc */ @@ -47,6 +59,10 @@ protected function createInstance(FixtureInterface $fixture) $arguments = []; } + if (null !== $this->namedArgumentsResolver) { + $arguments = $this->namedArgumentsResolver->resolveArguments($arguments, $factory, $method); + } + $instance = $factory::$method(...array_values($arguments)); if (false === $instance instanceof $class) { throw InstantiationExceptionFactory::createForInvalidInstanceType($fixture, $instance); diff --git a/src/Generator/NamedArgumentsResolver.php b/src/Generator/NamedArgumentsResolver.php new file mode 100644 index 000000000..0fc387d62 --- /dev/null +++ b/src/Generator/NamedArgumentsResolver.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Generator; + +class NamedArgumentsResolver +{ + public function resolveArguments(array $arguments, string $className, string $methodName): array + { + try { + $method = new \ReflectionMethod($className, $methodName); + } catch (\ReflectionException $exception) { + return $arguments; + } + + $sortedArguments = []; + $buffer = []; + + foreach ($method->getParameters() as $parameter) { + $name = $parameter->getName(); + + if ($parameter->isVariadic() && [] !== $arguments) { + $sortedArguments = array_merge($sortedArguments, $buffer, array_values($arguments)); + $arguments = []; + $buffer = []; + + break; + } + + if (array_key_exists($name, $arguments)) { + $sortedArguments = array_merge($sortedArguments, $buffer, [$name => $arguments[$name]]); + unset($arguments[$name]); + $buffer = []; + + continue; + } + + foreach ($arguments as $key => $value) { + if (is_int($key)) { + $sortedArguments = array_merge($sortedArguments, $buffer, [$arguments[$key]]); + unset($arguments[$key]); + $buffer = []; + + continue 2; + } + } + + if (!$parameter->isDefaultValueAvailable()) { + throw new \RuntimeException(sprintf( + 'Argument $%s of %s::%s() is not passed a value and does not define a default one.', + $name, + $className, + $methodName + )); + } + + $buffer[] = $parameter->getDefaultValue(); + } + + $unknownNamedParameters = array_filter(array_keys($arguments), static function ($key) { + return is_string($key); + }); + + if ([] !== $unknownNamedParameters) { + throw new \RuntimeException(sprintf( + 'Unknown arguments for %s::%s(): $%s.', + $className, + $methodName, + implode(', $', $unknownNamedParameters) + )); + } + + return $sortedArguments; + } +} diff --git a/src/Loader/NativeLoader.php b/src/Loader/NativeLoader.php index cf5a6fb02..7d163f8c2 100644 --- a/src/Loader/NativeLoader.php +++ b/src/Loader/NativeLoader.php @@ -116,6 +116,7 @@ use Nelmio\Alice\Generator\Instantiator\InstantiatorRegistry; use Nelmio\Alice\Generator\Instantiator\InstantiatorResolver; use Nelmio\Alice\Generator\InstantiatorInterface; +use Nelmio\Alice\Generator\NamedArgumentsResolver; use Nelmio\Alice\Generator\ObjectGenerator\CompleteObjectGenerator; use Nelmio\Alice\Generator\ObjectGenerator\SimpleObjectGenerator; use Nelmio\Alice\Generator\ObjectGeneratorInterface; @@ -204,6 +205,7 @@ * @method PropertyAccessorInterface getPropertyAccessor() * @method CallerInterface getCaller() * @method CallProcessorInterface getCallProcessor() + * @method NamedArgumentsResolver getNamedArgumentsResolver() */ class NativeLoader implements FilesLoaderInterface, FileLoaderInterface, DataLoaderInterface { @@ -589,18 +591,25 @@ protected function createFakerGenerator(): FakerGenerator protected function createInstantiator(): InstantiatorInterface { + $namedArgumentsResolver = $this->getNamedArgumentsResolver(); + return new ExistingInstanceInstantiator( new InstantiatorResolver( new InstantiatorRegistry([ - new NoCallerMethodCallInstantiator(), + new NoCallerMethodCallInstantiator($namedArgumentsResolver), new NullConstructorInstantiator(), new NoMethodCallInstantiator(), - new StaticFactoryInstantiator(), + new StaticFactoryInstantiator($namedArgumentsResolver), ]) ) ); } + private function createNamedArgumentsResolver(): NamedArgumentsResolver + { + return new NamedArgumentsResolver(); + } + protected function createHydrator(): HydratorInterface { return new SimpleHydrator( @@ -626,7 +635,7 @@ protected function createPropertyAccessor(): PropertyAccessorInterface protected function createCaller(): CallerInterface { - return new SimpleCaller($this->getCallProcessor(), $this->getValueResolver()); + return new SimpleCaller($this->getCallProcessor(), $this->getValueResolver(), $this->getNamedArgumentsResolver()); } protected function createCallProcessor(): CallProcessorInterface diff --git a/tests/Generator/NamedArgumentsResolverTest.php b/tests/Generator/NamedArgumentsResolverTest.php new file mode 100644 index 000000000..fd1ed4ee1 --- /dev/null +++ b/tests/Generator/NamedArgumentsResolverTest.php @@ -0,0 +1,330 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Nelmio\Alice\Generator; + +use Nelmio\Alice\Entity\DummyWithMethods; +use Nelmio\Alice\Entity\EmptyDummy; +use PHPUnit\Framework\TestCase; + +/** + * @covers \Nelmio\Alice\Generator\NamedArgumentsResolver + */ +class NamedArgumentsResolverTest extends TestCase +{ + /** + * @dataProvider provideResolveArgumentsCases + */ + public function testResolveArguments(string $className, string $methodName, array $argument, array $expectedResult) + { + $resolver = new NamedArgumentsResolver(); + + self::assertSame( + $expectedResult, + $resolver->resolveArguments($argument, $className, $methodName) + ); + } + + public function provideResolveArgumentsCases() + { + yield 'constructor: no named arguments' => [ + DummyWithMethods::class, + '__construct', + [ + 'value 1', + 'value 2', + ], + [ + 'value 1', + 'value 2', + ], + ]; + + yield 'constructor: named arguments' => [ + DummyWithMethods::class, + '__construct', + [ + 'foo1' => 'value 1', + 'foo2' => 'value 2', + ], + [ + 'foo1' => 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'constructor: named arguments in wrong order' => [ + DummyWithMethods::class, + '__construct', + [ + 'foo2' => 'value 2', + 'foo1' => 'value 1', + ], + [ + 'foo1' => 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'constructor: mix of anonymous and named arguments' => [ + DummyWithMethods::class, + '__construct', + [ + 'value 1', + 'foo2' => 'value 2', + ], + [ + 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'constructor: mix of anonymous and named arguments in wrong order' => [ + DummyWithMethods::class, + '__construct', + [ + 'foo2' => 'value 2', + 'value 1', + ], + [ + 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'static factory: no named arguments' => [ + DummyWithMethods::class, + 'create', + [ + 'value 1', + 'value 2', + ], + [ + 'value 1', + 'value 2', + ], + ]; + + yield 'static factory: named arguments' => [ + DummyWithMethods::class, + 'create', + [ + 'foo1' => 'value 1', + 'foo2' => 'value 2', + ], + [ + 'foo1' => 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'static factory: named arguments in wrong order' => [ + DummyWithMethods::class, + 'create', + [ + 'foo2' => 'value 2', + 'foo1' => 'value 1', + ], + [ + 'foo1' => 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'static factory: mix of anonymous and named arguments' => [ + DummyWithMethods::class, + 'create', + [ + 'value 1', + 'foo2' => 'value 2', + ], + [ + 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'static factory: mix of anonymous and named arguments in wrong order' => [ + DummyWithMethods::class, + 'create', + [ + 'foo2' => 'value 2', + 'value 1', + ], + [ + 'value 1', + 'foo2' => 'value 2', + ], + ]; + + yield 'method call: no named arguments' => [ + DummyWithMethods::class, + 'bar', + [ + 'value 1', + 'value 2', + ], + [ + 'value 1', + 'value 2', + ], + ]; + + yield 'method call: named arguments' => [ + DummyWithMethods::class, + 'bar', + [ + 'bar1' => 'value 1', + 'bar2' => 'value 2', + ], + [ + 'bar1' => 'value 1', + 'bar2' => 'value 2', + ], + ]; + + yield 'method call: named arguments in wrong order' => [ + DummyWithMethods::class, + 'bar', + [ + 'bar2' => 'value 2', + 'bar1' => 'value 1', + ], + [ + 'bar1' => 'value 1', + 'bar2' => 'value 2', + ], + ]; + + yield 'method call: mix of anonymous and named arguments' => [ + DummyWithMethods::class, + 'bar', + [ + 'value 1', + 'bar2' => 'value 2', + ], + [ + 'value 1', + 'bar2' => 'value 2', + ], + ]; + + yield 'method call: mix of anonymous and named arguments in wrong order' => [ + DummyWithMethods::class, + 'bar', + [ + 'bar2' => 'value 2', + 'value 1', + ], + [ + 'value 1', + 'bar2' => 'value 2', + ], + ]; + + yield 'with variadic argument' => [ + DummyWithMethods::class, + 'methodWithVariadic', + [ + 'baz2' => 'value 2', + 'value 1', + 'value 4', + 'baz3' => 'value 3', + 'value 5', + ], + [ + 'value 1', + 'baz2' => 'value 2', + 'value 4', + 'value 3', + 'value 5', + ], + ]; + + yield 'with missing arguments that have default values' => [ + DummyWithMethods::class, + 'methodWithDefaultValues', + [ + 'baz2' => 'value 2', + ], + [ + 'value 1', + 'baz2' => 'value 2', + ], + ]; + + yield 'with method that does not exist' => [ + EmptyDummy::class, + '__construct', + [ + 'unknown1' => 'value 1', + 'unknown2' => 'value 2', + ], + [ + 'unknown1' => 'value 1', + 'unknown2' => 'value 2', + ], + ]; + + yield 'with numeric string keys' => [ + DummyWithMethods::class, + '__construct', + [ + '0' => 'value 1', + '1' => 'value 2', + ], + [ + '0' => 'value 1', + '1' => 'value 2', + ], + ]; + + yield 'with null passed to an argument that has nullable type' => [ + DummyWithMethods::class, + 'methodWithNullables', + [ + 'bar1' => 'hello', + 'bar2' => null, + ], + [ + 'bar1' => 'hello', + 'bar2' => null, + ], + ]; + } + + public function testThrowsExceptionWhenResolvingUnknownArguments() + { + $resolver = new NamedArgumentsResolver(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Unknown arguments for Nelmio\Alice\Entity\DummyWithMethods::bar(): $unknown1, $unknown2.'); + + $resolver->resolveArguments([ + 'bar1' => 'value 1', + 'bar2' => 'value 2', + 'unknown1' => 'value 3', + 'unknown2' => 'value 4', + ], DummyWithMethods::class, 'bar'); + } + + public function testThrowsExceptionWhenMissingArgumentsDontHaveDefaultValues() + { + $resolver = new NamedArgumentsResolver(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Argument $bar1 of Nelmio\Alice\Entity\DummyWithMethods::bar() is not passed a value and does not define a default one.'); + + $resolver->resolveArguments([], DummyWithMethods::class, 'bar'); + } +} diff --git a/tests/Loader/LoaderIntegrationTest.php b/tests/Loader/LoaderIntegrationTest.php index 08242b283..504e0bcb5 100644 --- a/tests/Loader/LoaderIntegrationTest.php +++ b/tests/Loader/LoaderIntegrationTest.php @@ -1532,19 +1532,6 @@ public function provideFixturesToInstantiate() new FixtureEntity\Instantiator\DummyWithExplicitDefaultConstructor(), ]; -// yield 'with named constructor - use factory function' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructor::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [], -// ], -// ], -// ], -// ], -// FixtureEntity\Instantiator\DummyWithNamedConstructor::namedConstruct(), -// ]; - yield 'with default constructor and optional parameters without parameters - use constructor function' => [ [ FixtureEntity\Instantiator\DummyWithOptionalParameterInConstructor::class => [ @@ -1601,107 +1588,6 @@ public function provideFixturesToInstantiate() new FixtureEntity\Instantiator\DummyWithRequiredParameterInConstructor(100), ]; -// yield 'with named constructor and optional parameters with no parameters - use factory function' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndOptionalParameters::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [], -// ], -// ], -// ], -// ], -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndOptionalParameters::namedConstruct(), -// ]; -// -// yield 'with named constructor and optional parameters with parameters - use factory function' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndOptionalParameters::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [ -// 100, -// ], -// ], -// ], -// ], -// ], -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndOptionalParameters::namedConstruct(100), -// ]; -// -// yield 'with named constructor and optional parameters with parameters and unique value - use factory function' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndOptionalParameters::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [ -// '0 (unique)' => 100, -// ], -// ], -// ], -// ], -// ], -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndOptionalParameters::namedConstruct(100), -// ]; -// -// yield 'with named constructor and required parameters with no parameters - throw exception' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndRequiredParameters::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [], -// ], -// ], -// ], -// ], -// 'An error occurred while generating the fixture "dummy" (Nelmio\Alice\Entity\Instantiator\DummyWithNamedConstructorAndRequiredParameters): Could not instantiate fixture "dummy".', -// GenerationThrowable::class, -// ]; -// -// yield 'with named constructor and required parameters with parameters - use factory function' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndRequiredParameters::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [ -// 100, -// ], -// ], -// ], -// ], -// ], -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndRequiredParameters::namedConstruct(100), -// ]; -// -// yield 'with named constructor and required parameters with named parameters - use factory function' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndRequiredParameters::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [ -// 'param' => 100, -// ], -// ], -// ], -// ], -// ], -// FixtureEntity\Instantiator\DummyWithNamedConstructorAndRequiredParameters::namedConstruct(100), -// ]; -// -// yield 'with unknown named constructor' => [ -// [ -// FixtureEntity\Instantiator\DummyWithDefaultConstructor::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'unknown' => [], -// ], -// ], -// ], -// ], -// 'An error occurred while generating the fixture "dummy" (Nelmio\Alice\Entity\Instantiator\DummyWithDefaultConstructor): Could not instantiate fixture "dummy".', -// GenerationThrowable::class, -// ]; -// yield 'with private constructor – throw exception' => [ [ FixtureEntity\Instantiator\DummyWithPrivateConstructor::class => [ @@ -1721,21 +1607,7 @@ public function provideFixturesToInstantiate() 'An error occurred while generating the fixture "dummy" (Nelmio\Alice\Entity\Instantiator\DummyWithProtectedConstructor): Could not instantiate "dummy", the constructor of "Nelmio\Alice\Entity\Instantiator\DummyWithProtectedConstructor" is not public.', GenerationThrowable::class, ]; -// -// yield 'with private named constructor – throw exception' => [ -// [ -// FixtureEntity\Instantiator\DummyWithNamedPrivateConstructor::class => [ -// 'dummy' => [ -// '__construct' => [ -// 'namedConstruct' => [], -// ], -// ], -// ], -// ], -// 'An error occurred while generating the fixture "dummy" (Nelmio\Alice\Entity\Instantiator\DummyWithNamedPrivateConstructor): Could not instantiate fixture "dummy".', -// GenerationThrowable::class, -// ]; -// + yield 'with default constructor but specified no constructor – use reflection' => [ [ FixtureEntity\Instantiator\DummyWithDefaultConstructor::class => [ @@ -1847,6 +1719,61 @@ public function provideFixturesToInstantiate() ], (new ReflectionClass(FixtureEntity\Instantiator\DummyWithNamedPrivateConstructor::class))->newInstanceWithoutConstructor(), ]; + + yield 'with constructor named parameters' => [ + [ + FixtureEntity\DummyWithMethods::class => [ + 'dummy' => [ + '__construct' => [ + 'foo2' => 'value 2', + 'foo1' => 'value 1', + ], + ], + ], + ], + new FixtureEntity\DummyWithMethods('value 1', 'value 2'), + ]; + + yield 'with factory named parameters' => [ + [ + FixtureEntity\DummyWithMethods::class => [ + 'dummy' => [ + '__factory' => [ + 'create' => [ + 'foo2' => 'value 2', + 'foo1' => 'value 1', + ], + ], + ], + ], + ], + FixtureEntity\DummyWithMethods::create('value 1', 'value 2'), + ]; + + $expected = new FixtureEntity\DummyWithMethods('value 1', 'value 2'); + $expected->bar('value 3', 'value 4'); + + yield 'with method call named parameters' => [ + [ + FixtureEntity\DummyWithMethods::class => [ + 'dummy' => [ + '__construct' => [ + 'foo2' => 'value 2', + 'foo1' => 'value 1', + ], + '__calls' => [ + [ + 'bar' => [ + 'bar2' => 'value 4', + 'bar1' => 'value 3', + ], + ] + ], + ], + ], + ], + $expected, + ]; } public function provideLegacyFixturesToInstantiate() diff --git a/tests/Parser/Chainable/JsonParserTest.php b/tests/Parser/Chainable/JsonParserTest.php index f3639d0d5..335b17f67 100644 --- a/tests/Parser/Chainable/JsonParserTest.php +++ b/tests/Parser/Chainable/JsonParserTest.php @@ -143,6 +143,33 @@ public function testParsingEmptyFileResultsInEmptySet() $this->assertSame([], $actual); } + public function testParseReturnsNamedParameters() + { + $actual = $this->parser->parse(self::$dir.'/named_parameters.json'); + + $this->assertSame( + [ + 'Nelmio\Alice\DummyWithMethods' => [ + 'dummy_with_methods' => [ + '__construct' => [ + '$foo1' => 'foo1', + '$foo2' => 'foo2', + ], + '__calls' => [ + [ + 'bar' => [ + '$bar1' => 'bar1', + '$bar2' => 'bar2', + ], + ], + ], + ], + ], + ], + $actual + ); + } + public function testThrowsAnExceptionIfInvalidJson() { $this->expectException(UnparsableFileException::class); diff --git a/tests/Parser/Chainable/PhpParserTest.php b/tests/Parser/Chainable/PhpParserTest.php index 70da7b850..abf39be7e 100644 --- a/tests/Parser/Chainable/PhpParserTest.php +++ b/tests/Parser/Chainable/PhpParserTest.php @@ -142,6 +142,33 @@ public function testParsingEmptyFileResultsInEmptySet() $this->assertSame([], $actual); } + public function testParseReturnsNamedParameters() + { + $actual = $this->parser->parse(self::$dir.'/named_parameters.php'); + + $this->assertSame( + [ + 'Nelmio\Alice\DummyWithMethods' => [ + 'dummy_with_methods' => [ + '__construct' => [ + '$foo1' => 'foo1', + '$foo2' => 'foo2', + ], + '__calls' => [ + [ + 'bar' => [ + '$bar1' => 'bar1', + '$bar2' => 'bar2', + ], + ], + ], + ], + ], + ], + $actual + ); + } + public function testThrowsAnExceptionIfNoArrayReturnedInParsedFile() { $this->expectException(\TypeError::class); diff --git a/tests/Parser/Chainable/YamlParserTest.php b/tests/Parser/Chainable/YamlParserTest.php index f9e20854b..4c5f6608a 100644 --- a/tests/Parser/Chainable/YamlParserTest.php +++ b/tests/Parser/Chainable/YamlParserTest.php @@ -222,6 +222,36 @@ public function testParsingEmptyFileResultsInEmptySet() $this->assertSame([], $actual); } + public function testParseReturnsNamedParameters() + { + $symfonyParser = new SymfonyYamlParser(); + + $parser = new YamlParser($symfonyParser); + $actual = $parser->parse(self::$dir.'/named_parameters.yml'); + + $this->assertSame( + [ + 'Nelmio\Alice\DummyWithMethods' => [ + 'dummy_with_methods' => [ + '__construct' => [ + '$foo1' => 'foo1', + '$foo2' => 'foo2', + ], + '__calls' => [ + [ + 'bar' => [ + '$bar1' => 'bar1', + '$bar2' => 'bar2', + ], + ], + ], + ], + ], + ], + $actual + ); + } + public function testThrowsAnExceptionIfFileNotParsable() { try {