diff --git a/features/sanity_checks/context_constructor_dependency_injection_compatibility.feature b/features/sanity_checks/context_constructor_dependency_injection_compatibility.feature new file mode 100644 index 0000000..8b6c005 --- /dev/null +++ b/features/sanity_checks/context_constructor_dependency_injection_compatibility.feature @@ -0,0 +1,56 @@ +Feature: Context constructor dependency injection compatibility + + Scenario: Using context consturctor dependency injection + Given a working Symfony application with SymfonyExtension configured + And a Behat configuration containing: + """ + default: + suites: + default: + contexts: + - App\Tests\SomeContext: + - "@App\\Foo" + + services: + App\Foo: ~ + """ + And a class file "src/Foo.php" containing: + """ + foo = $foo; + } + + /** @Then it should pass */ + public function itShouldPass(): void + { + assert($this->foo instanceof Foo); + } + } + """ + When I run Behat + Then it should pass diff --git a/src/Context/Environment/Handler/ContextServiceEnvironmentHandler.php b/src/Context/Environment/Handler/ContextServiceEnvironmentHandler.php index 88130f9..d64c1f6 100644 --- a/src/Context/Environment/Handler/ContextServiceEnvironmentHandler.php +++ b/src/Context/Environment/Handler/ContextServiceEnvironmentHandler.php @@ -13,9 +13,13 @@ declare(strict_types=1); namespace FriendsOfBehat\SymfonyExtension\Context\Environment\Handler; +use Behat\Behat\Context\Argument\ArgumentResolverFactory; +use Behat\Behat\Context\Argument\NullFactory; +use Behat\Behat\Context\Argument\SuiteScopedResolverFactory; +use Behat\Behat\Context\Argument\SuiteScopedResolverFactoryAdapter; use Behat\Behat\Context\Context; use Behat\Behat\Context\ContextClass\ClassResolver; -use Behat\Behat\Context\Initializer\ContextInitializer; +use Behat\Behat\Context\ContextFactory; use Behat\Testwork\Environment\Environment; use Behat\Testwork\Environment\Exception\EnvironmentIsolationException; use Behat\Testwork\Environment\Handler\EnvironmentHandler; @@ -32,15 +36,29 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler /** @var KernelInterface */ private $symfonyKernel; - /** @var ContextInitializer[] */ - private $contextInitializers = []; - /** @var ClassResolver[] */ private $classResolvers = []; - public function __construct(KernelInterface $symfonyKernel) + /** @var ContextFactory */ + private $contextFactory; + + /** @var ArgumentResolverFactory */ + private $resolverFactory; + + /** + * @param ArgumentResolverFactory|SuiteScopedResolverFactory $resolverFactory + */ + public function __construct(KernelInterface $symfonyKernel, ContextFactory $factory, $resolverFactory = null) { $this->symfonyKernel = $symfonyKernel; + + $this->contextFactory = $factory; + + if ($resolverFactory && !$resolverFactory instanceof ArgumentResolverFactory) { + $resolverFactory = new SuiteScopedResolverFactoryAdapter($resolverFactory); + } + + $this->resolverFactory = $resolverFactory ?: new NullFactory(); } public function supportsSuite(Suite $suite): bool @@ -51,8 +69,8 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler public function buildEnvironment(Suite $suite): Environment { $environment = new UninitialisedContextServiceEnvironment($suite); - foreach ($this->getSuiteContextsServices($suite) as $contextId) { - $environment->registerContextService($contextId, $this->getContextClass($contextId)); + foreach ($this->getSuiteContextsServices($suite) as [$contextId, $contextArguments]) { + $environment->registerContextService($contextId, $this->getContextClass($contextId), $contextArguments); } return $environment; @@ -73,28 +91,24 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler $this->assertEnvironmentCanBeIsolated($uninitializedEnvironment, $testSubject); $environment = new InitialisedContextServiceEnvironment($uninitializedEnvironment->getSuite()); - foreach ($uninitializedEnvironment->getContextServices() as $contextId) { + $resolvers = $this->resolverFactory->createArgumentResolvers($environment); + + foreach ($uninitializedEnvironment->getContextServicesWithArguments() as $contextId => $arguments) { /** @var Context $context */ - $context = $this->getContext($contextId); - $this->initializeInstance($context); + $context = $this->getContext($contextId, $arguments, $resolvers); $environment->registerContext($context); } return $environment; } - public function registerContextInitializer(ContextInitializer $contextInitializer): void - { - $this->contextInitializers[] = $contextInitializer; - } - public function registerClassResolver(ClassResolver $classResolver): void { $this->classResolvers[] = $classResolver; } /** - * @return string[] + * @return array[] * * @throws SuiteConfigurationException If "contexts" setting is not an array */ @@ -110,7 +124,20 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler ), $suite->getName()); } - return $contextsServices; + return array_map( + function ($context): array { + $class = $context; + $arguments = []; + + if (is_array($context)) { + $class = current(array_keys($context)); + $arguments = $context[$class]; + } + + return [$class, $arguments]; + }, + $contextsServices + ); } /** @@ -127,13 +154,6 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler } } - private function initializeInstance(Context $context): void - { - foreach ($this->contextInitializers as $initializer) { - $initializer->initializeContext($context); - } - } - private function resolveContextId(string $contextId): string { foreach ($this->classResolvers as $resolver) { @@ -162,7 +182,7 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler throw new \DomainException(sprintf('There is no service or class "%s".', $contextId)); } - private function getContext(string $contextId): Context + private function getContext(string $contextId, array $arguments = [], array $resolvers = []): Context { $contextId = $this->resolveContextId($contextId); @@ -171,7 +191,7 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler if ($this->getContainer()->has($contextId)) { $context = $this->getContainer()->get($contextId); } elseif (class_exists($class)) { - $context = new $class(); + $context = $this->contextFactory->createContext($class, $arguments, $resolvers); } else { throw new \DomainException(sprintf('There is no service or class "%s".', $contextId)); } diff --git a/src/Context/Environment/UninitialisedContextServiceEnvironment.php b/src/Context/Environment/UninitialisedContextServiceEnvironment.php index bbd6211..abc8a4a 100644 --- a/src/Context/Environment/UninitialisedContextServiceEnvironment.php +++ b/src/Context/Environment/UninitialisedContextServiceEnvironment.php @@ -24,9 +24,12 @@ final class UninitialisedContextServiceEnvironment extends StaticEnvironment imp /** @var string[] */ private $contextServices = []; - public function registerContextService(string $serviceId, string $serviceClass): void + public function registerContextService(string $serviceId, string $serviceClass, array $arguments = []): void { - $this->contextServices[$serviceId] = $serviceClass; + $this->contextServices[$serviceId] = [ + 'class' => $serviceClass, + 'arguments' => $arguments, + ]; } public function getContextServices(): array @@ -41,11 +44,20 @@ final class UninitialisedContextServiceEnvironment extends StaticEnvironment imp public function getContextClasses(): array { - return array_values($this->contextServices); + return array_map(function (array $contextDetails): string { + return $contextDetails['class']; + }, $this->contextServices); } public function hasContextClass($class): bool { - return in_array($class, $this->contextServices, true); + return in_array($class, $this->getContextClasses(), true); + } + + public function getContextServicesWithArguments(): iterable + { + foreach ($this->contextServices as $contextDetails) { + yield $contextDetails['class'] => $contextDetails['arguments']; + } } } diff --git a/src/ServiceContainer/SymfonyExtension.php b/src/ServiceContainer/SymfonyExtension.php index 7810a0c..bf99aa7 100644 --- a/src/ServiceContainer/SymfonyExtension.php +++ b/src/ServiceContainer/SymfonyExtension.php @@ -137,6 +137,8 @@ final class SymfonyExtension implements Extension { $definition = new Definition(ContextServiceEnvironmentHandler::class, [ new Reference(self::KERNEL_ID), + new Reference(ContextExtension::FACTORY_ID), + new Reference(ContextExtension::AGGREGATE_RESOLVER_FACTORY_ID) ]); $definition->addTag(EnvironmentExtension::HANDLER_TAG, ['priority' => 128]); @@ -250,10 +252,6 @@ final class SymfonyExtension implements Extension { $definition = $container->findDefinition('fob_symfony.environment_handler.context_service'); - foreach ($container->findTaggedServiceIds(ContextExtension::INITIALIZER_TAG) as $serviceId => $tags) { - $definition->addMethodCall('registerContextInitializer', [$container->getDefinition($serviceId)]); - } - foreach ($container->findTaggedServiceIds(ContextExtension::CLASS_RESOLVER_TAG) as $serviceId => $tags) { $definition->addMethodCall('registerClassResolver', [$container->getDefinition($serviceId)]); }