├── easy-coding-standard.neon ├── src ├── Context │ ├── Environment │ │ ├── ContextServiceEnvironment.php │ │ ├── UninitialisedContextServiceEnvironment.php │ │ ├── InitialisedContextServiceEnvironment.php │ │ └── Handler │ │ │ └── ContextServiceEnvironmentHandler.php │ └── ContextRegistry.php ├── Listener │ └── ScenarioContainerResetter.php └── ServiceContainer │ ├── Scenario │ ├── ContextRegistryPass.php │ └── ContainerFactory.php │ └── ContextServiceExtension.php ├── LICENSE ├── composer.json └── README.md /easy-coding-standard.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/sylius-labs/coding-standard/easy-coding-standard.neon 3 | 4 | checkers: 5 | PhpCsFixer\Fixer\ControlStructure\YodaStyleFixer: 6 | equal: false 7 | identical: false 8 | less_and_greater: false 9 | SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff: {} 10 | -------------------------------------------------------------------------------- /src/Context/Environment/ContextServiceEnvironment.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\Context\Environment; 15 | 16 | use Behat\Behat\Context\Environment\ContextEnvironment; 17 | use FriendsOfBehat\ContextServiceExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler; 18 | 19 | /** 20 | * @see ContextServiceEnvironmentHandler 21 | */ 22 | interface ContextServiceEnvironment extends ContextEnvironment 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2017 Kamil Kokot 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friends-of-behat/context-service-extension", 3 | "description": "Allows to declare and use contexts services in scenario scoped container.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Kamil Kokot", 8 | "email": "kamil@kokot.me", 9 | "homepage": "http://kamil.kokot.me" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.1", 14 | 15 | "behat/behat": "^3.1", 16 | "symfony/dependency-injection": "^3.4|^4.1" 17 | }, 18 | "require-dev": { 19 | "friends-of-behat/cross-container-extension": "^1.0", 20 | "friends-of-behat/test-context": "^1.0", 21 | "phpspec/phpspec": "^5.0" 22 | }, 23 | "suggest": { 24 | "friends-of-behat/cross-container-extension": "^1.0", 25 | "ocramius/proxy-manager": "^2.0", 26 | "symfony/proxy-manager-bridge": "^3.4|^4.2" 27 | }, 28 | "autoload": { 29 | "psr-4": { "FriendsOfBehat\\ContextServiceExtension\\": "src/" } 30 | }, 31 | "autoload-dev": { 32 | "psr-4" : { "spec\\FriendsOfBehat\\ContextServiceExtension\\": "spec/" } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Context/ContextRegistry.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\Context; 15 | 16 | /** 17 | * @internal 18 | */ 19 | final class ContextRegistry 20 | { 21 | /** 22 | * @var string[] 23 | */ 24 | private $registry; 25 | 26 | /** 27 | * @param string $serviceId 28 | * @param string $serviceClass 29 | */ 30 | public function add(string $serviceId, string $serviceClass): void 31 | { 32 | $this->registry[$serviceId] = $serviceClass; 33 | } 34 | 35 | /** 36 | * @param string $serviceId 37 | * 38 | * @return string 39 | * 40 | * @throws \InvalidArgumentException 41 | */ 42 | public function getClass(string $serviceId): string 43 | { 44 | if (!isset($this->registry[$serviceId])) { 45 | throw new \InvalidArgumentException(sprintf( 46 | 'Could not find class for service with id "%s".', 47 | $serviceId 48 | )); 49 | } 50 | 51 | return $this->registry[$serviceId]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Listener/ScenarioContainerResetter.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\Listener; 15 | 16 | use Behat\Behat\EventDispatcher\Event\ExampleTested; 17 | use Behat\Behat\EventDispatcher\Event\ScenarioTested; 18 | use Symfony\Component\DependencyInjection\ResettableContainerInterface; 19 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 20 | 21 | /** 22 | * @internal 23 | */ 24 | final class ScenarioContainerResetter implements EventSubscriberInterface 25 | { 26 | /** 27 | * @var ResettableContainerInterface 28 | */ 29 | private $scenarioContainer; 30 | 31 | /** 32 | * @param ResettableContainerInterface $scenarioContainer 33 | */ 34 | public function __construct(ResettableContainerInterface $scenarioContainer) 35 | { 36 | $this->scenarioContainer = $scenarioContainer; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public static function getSubscribedEvents(): array 43 | { 44 | return [ 45 | ScenarioTested::AFTER => ['reset', -15], 46 | ExampleTested::AFTER => ['reset', -15], 47 | ]; 48 | } 49 | 50 | public function reset(): void 51 | { 52 | $this->scenarioContainer->reset(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ServiceContainer/Scenario/ContextRegistryPass.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\ServiceContainer\Scenario; 15 | 16 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\DependencyInjection\Definition; 19 | 20 | /** 21 | * @internal 22 | */ 23 | final class ContextRegistryPass implements CompilerPassInterface 24 | { 25 | const CONTEXT_SERVICE_TAG = 'fob.context_service'; 26 | 27 | /** 28 | * @var Definition 29 | */ 30 | private $contextRegistryDefinition; 31 | 32 | /** 33 | * @param Definition $contextRegistryDefinition 34 | */ 35 | public function __construct(Definition $contextRegistryDefinition) 36 | { 37 | $this->contextRegistryDefinition = $contextRegistryDefinition; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function process(ContainerBuilder $container): void 44 | { 45 | $taggedServices = $container->findTaggedServiceIds(self::CONTEXT_SERVICE_TAG); 46 | 47 | foreach ($taggedServices as $id => $tags) { 48 | $container->getDefinition($id)->setPublic(true); 49 | 50 | $this->contextRegistryDefinition->addMethodCall( 51 | 'add', 52 | [$id, $container->findDefinition($id)->getClass()] 53 | ); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Context/Environment/UninitialisedContextServiceEnvironment.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\Context\Environment; 15 | 16 | use Behat\Testwork\Environment\StaticEnvironment; 17 | use FriendsOfBehat\ContextServiceExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler; 18 | 19 | /** 20 | * @internal 21 | * 22 | * @see ContextServiceEnvironmentHandler 23 | */ 24 | final class UninitialisedContextServiceEnvironment extends StaticEnvironment implements ContextServiceEnvironment 25 | { 26 | /** 27 | * @var string[] 28 | */ 29 | private $contextServices = []; 30 | 31 | /** 32 | * @param string $serviceId 33 | * @param string $serviceClass 34 | */ 35 | public function registerContextService(string $serviceId, string $serviceClass): void 36 | { 37 | $this->contextServices[$serviceId] = $serviceClass; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function getContextServices(): array 44 | { 45 | return array_keys($this->contextServices); 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function hasContexts(): bool 52 | { 53 | return count($this->contextServices) > 0; 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | public function getContextClasses(): array 60 | { 61 | return array_values($this->contextServices); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function hasContextClass($class): bool 68 | { 69 | return in_array($class, $this->contextServices, true); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ServiceContainer/Scenario/ContainerFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\ServiceContainer\Scenario; 15 | 16 | use ProxyManager\Configuration as ProxyManagerConfiguration; 17 | use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; 18 | use Symfony\Component\Config\FileLocator; 19 | use Symfony\Component\Config\Loader\DelegatingLoader; 20 | use Symfony\Component\Config\Loader\LoaderInterface; 21 | use Symfony\Component\Config\Loader\LoaderResolver; 22 | use Symfony\Component\DependencyInjection\ContainerBuilder; 23 | use Symfony\Component\DependencyInjection\Loader; 24 | 25 | /** 26 | * @internal 27 | */ 28 | final class ContainerFactory 29 | { 30 | /** 31 | * @param string $basePath 32 | * @param array $importedFiles 33 | * 34 | * @return ContainerBuilder 35 | */ 36 | public function createContainer(string $basePath, array $importedFiles = []): ContainerBuilder 37 | { 38 | $container = new ContainerBuilder(); 39 | 40 | $this->enableSupportForLazyServicesIfPossible($container); 41 | 42 | $loader = $this->createLoader($container, $basePath); 43 | foreach ($importedFiles as $file) { 44 | $type = false !== mb_strpos($file, '*') ? 'glob' : null; 45 | $loader->load($file, $type); 46 | } 47 | 48 | return $container; 49 | } 50 | 51 | /** 52 | * @param ContainerBuilder $container 53 | * @param string $basePath 54 | * 55 | * @return LoaderInterface 56 | */ 57 | private function createLoader(ContainerBuilder $container, string $basePath): LoaderInterface 58 | { 59 | $fileLocator = new FileLocator($basePath); 60 | $loader = new DelegatingLoader(new LoaderResolver([ 61 | new Loader\XmlFileLoader($container, $fileLocator), 62 | new Loader\YamlFileLoader($container, $fileLocator), 63 | new Loader\PhpFileLoader($container, $fileLocator), 64 | new Loader\GlobFileLoader($container, $fileLocator) 65 | ])); 66 | 67 | return $loader; 68 | } 69 | 70 | /** 71 | * @param ContainerBuilder $container 72 | */ 73 | private function enableSupportForLazyServicesIfPossible(ContainerBuilder $container): void 74 | { 75 | if (class_exists(ProxyManagerConfiguration::class) && class_exists(RuntimeInstantiator::class)) { 76 | $container->setProxyInstantiator(new RuntimeInstantiator()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Context/Environment/InitialisedContextServiceEnvironment.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\Context\Environment; 15 | 16 | use Behat\Behat\Context\Context; 17 | use Behat\Behat\Context\Exception\ContextNotFoundException; 18 | use Behat\Testwork\Call\Callee; 19 | use Behat\Testwork\Suite\Suite; 20 | use FriendsOfBehat\ContextServiceExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler; 21 | 22 | /** 23 | * @internal 24 | * 25 | * @see ContextServiceEnvironmentHandler 26 | */ 27 | final class InitialisedContextServiceEnvironment implements ContextServiceEnvironment 28 | { 29 | /** 30 | * @var Suite 31 | */ 32 | private $suite; 33 | 34 | /** 35 | * @var Context[] 36 | */ 37 | private $contexts = []; 38 | 39 | /** 40 | * @param Suite $suite 41 | */ 42 | public function __construct(Suite $suite) 43 | { 44 | $this->suite = $suite; 45 | } 46 | 47 | /** 48 | * @param Context $context 49 | */ 50 | public function registerContext(Context $context): void 51 | { 52 | $this->contexts[get_class($context)] = $context; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function getSuite(): Suite 59 | { 60 | return $this->suite; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function bindCallee(Callee $callee): callable 67 | { 68 | $callable = $callee->getCallable(); 69 | 70 | if ($callee->isAnInstanceMethod()) { 71 | return [$this->getContext($callable[0]), $callable[1]]; 72 | } 73 | 74 | return $callable; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function hasContexts(): bool 81 | { 82 | return count($this->contexts) > 0; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function getContextClasses(): array 89 | { 90 | return array_keys($this->contexts); 91 | } 92 | 93 | /** 94 | * {@inheritdoc} 95 | */ 96 | public function hasContextClass($class): bool 97 | { 98 | return isset($this->contexts[$class]); 99 | } 100 | 101 | /** 102 | * @param string $class 103 | * 104 | * @return Context 105 | * 106 | * @throws ContextNotFoundException 107 | */ 108 | private function getContext(string $class): Context 109 | { 110 | if (!isset($this->contexts[$class])) { 111 | throw new ContextNotFoundException(sprintf( 112 | '`%s` context is not found in the suite environment. Have you registered it?', 113 | $class 114 | ), $class); 115 | } 116 | 117 | return $this->contexts[$class]; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Context Service Extension [![License](https://img.shields.io/packagist/l/friends-of-behat/context-service-extension.svg)](https://packagist.org/packages/friends-of-behat/context-service-extension) [![Version](https://img.shields.io/packagist/v/friends-of-behat/context-service-extension.svg)](https://packagist.org/packages/friends-of-behat/context-service-extension) [![Build status on Linux](https://img.shields.io/travis/FriendsOfBehat/ContextServiceExtension/master.svg)](http://travis-ci.org/FriendsOfBehat/ContextServiceExtension) [![Scrutinizer Quality Score](https://img.shields.io/scrutinizer/g/FriendsOfBehat/ContextServiceExtension.svg)](https://scrutinizer-ci.com/g/FriendsOfBehat/ContextServiceExtension/) 2 | 3 | #### This library is deprecated! It will not be supported anymore - if you're using it with CrossContainerExtension v1 and SymfonyExtension v1, please consider an upgrade to [SymfonyExtension v2](https://github.com/FriendsOfBehat/SymfonyExtension). 4 | 5 | Allows to declare and use contexts services in scenario scoped container. 6 | 7 | ## Usage 8 | 9 | 1. Install it: 10 | 11 | ```bash 12 | $ composer require friends-of-behat/context-service-extension --dev 13 | ``` 14 | 15 | 2. Enable and configure context service extension in your Behat configuration: 16 | 17 | ```yaml 18 | # behat.yml 19 | default: 20 | # ... 21 | extensions: 22 | FriendsOfBehat\ContextServiceExtension: 23 | imports: 24 | - "features/bootstrap/config/services.xml" 25 | - "features/bootstrap/config/services.yml" 26 | - "features/bootstrap/config/services.php" 27 | ``` 28 | 29 | 3. Inside one of those files passed to configuration above, create a service tagged with `fob.context_service`. 30 | 31 | ```xml 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | ```yaml 44 | # features/bootstrap/config/services.yml 45 | services: 46 | acme.my_context: 47 | class: Acme\MyContext 48 | tags: 49 | - { name: fob.context_service } 50 | ``` 51 | 52 | ```php 53 | // features/bootstrap/config/services.php 54 | use Symfony\Component\DependencyInjection\Definition; 55 | 56 | $definition = new Definition(\Acme\MyContext::class); 57 | $definition->addTag('fob.context_service'); 58 | $container->setDefinition('acme.my_context', $definition); 59 | ``` 60 | 61 | 4. Configure your suite to use `acme.my_context` context service (note **contexts_services** key instead of **contexts**): 62 | 63 | ```yaml 64 | # behat.yml 65 | default: 66 | # ... 67 | suites: 68 | default: 69 | contexts_services: 70 | - acme.my_context 71 | ``` 72 | 73 | 5. Have fun with your contexts defined in DI container as services! :tada: 74 | -------------------------------------------------------------------------------- /src/Context/Environment/Handler/ContextServiceEnvironmentHandler.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\Context\Environment\Handler; 15 | 16 | use Behat\Behat\Context\Context; 17 | use Behat\Behat\Context\Initializer\ContextInitializer; 18 | use Behat\Testwork\Environment\Environment; 19 | use Behat\Testwork\Environment\Exception\EnvironmentIsolationException; 20 | use Behat\Testwork\Environment\Handler\EnvironmentHandler; 21 | use Behat\Testwork\Suite\Exception\SuiteConfigurationException; 22 | use Behat\Testwork\Suite\Suite; 23 | use FriendsOfBehat\ContextServiceExtension\Context\ContextRegistry; 24 | use FriendsOfBehat\ContextServiceExtension\Context\Environment\InitialisedContextServiceEnvironment; 25 | use FriendsOfBehat\ContextServiceExtension\Context\Environment\UninitialisedContextServiceEnvironment; 26 | use Symfony\Component\DependencyInjection\ContainerInterface; 27 | 28 | /** 29 | * @internal 30 | */ 31 | final class ContextServiceEnvironmentHandler implements EnvironmentHandler 32 | { 33 | /** 34 | * @var ContainerInterface 35 | */ 36 | private $container; 37 | 38 | /** 39 | * @var ContextRegistry 40 | */ 41 | private $contextRegistry; 42 | 43 | /** 44 | * @var ContextInitializer[] 45 | */ 46 | private $contextInitializers = []; 47 | 48 | /** 49 | * @param ContainerInterface $container 50 | * @param ContextRegistry $contextRegistry 51 | */ 52 | public function __construct(ContainerInterface $container, ContextRegistry $contextRegistry) 53 | { 54 | $this->container = $container; 55 | $this->contextRegistry = $contextRegistry; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function supportsSuite(Suite $suite): bool 62 | { 63 | return $suite->hasSetting('contexts_services'); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function buildEnvironment(Suite $suite): Environment 70 | { 71 | $environment = new UninitialisedContextServiceEnvironment($suite); 72 | foreach ($this->getSuiteContextsServices($suite) as $serviceId) { 73 | $environment->registerContextService($serviceId, $this->contextRegistry->getClass($serviceId)); 74 | } 75 | 76 | return $environment; 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null): bool 83 | { 84 | return $environment instanceof UninitialisedContextServiceEnvironment; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | * 90 | * @throws EnvironmentIsolationException 91 | */ 92 | public function isolateEnvironment(Environment $uninitializedEnvironment, $testSubject = null): Environment 93 | { 94 | /** @var UninitialisedContextServiceEnvironment $uninitializedEnvironment */ 95 | $this->assertEnvironmentCanBeIsolated($uninitializedEnvironment, $testSubject); 96 | 97 | $environment = new InitialisedContextServiceEnvironment($uninitializedEnvironment->getSuite()); 98 | foreach ($uninitializedEnvironment->getContextServices() as $serviceId) { 99 | /** @var Context $context */ 100 | $context = $this->container->get($serviceId); 101 | $this->initializeInstance($context); 102 | $environment->registerContext($context); 103 | } 104 | 105 | return $environment; 106 | } 107 | 108 | /** 109 | * @param Suite $suite 110 | * 111 | * @return string[] 112 | * 113 | * @throws SuiteConfigurationException If "contexts_services" setting is not an array 114 | */ 115 | private function getSuiteContextsServices(Suite $suite): array 116 | { 117 | $contextsServices = $suite->getSetting('contexts_services'); 118 | 119 | if (!is_array($contextsServices)) { 120 | throw new SuiteConfigurationException(sprintf( 121 | '"contexts_services" setting of the "%s" suite is expected to be an array, %s given.', 122 | $suite->getName(), 123 | gettype($contextsServices) 124 | ), $suite->getName()); 125 | } 126 | 127 | return $contextsServices; 128 | } 129 | 130 | /** 131 | * @param Environment $uninitializedEnvironment 132 | * @param mixed $testSubject 133 | * 134 | * @throws EnvironmentIsolationException 135 | */ 136 | private function assertEnvironmentCanBeIsolated(Environment $uninitializedEnvironment, $testSubject): void 137 | { 138 | if (!$this->supportsEnvironmentAndSubject($uninitializedEnvironment, $testSubject)) { 139 | throw new EnvironmentIsolationException(sprintf( 140 | '"%s" does not support isolation of "%s" environment.', 141 | static::class, 142 | get_class($uninitializedEnvironment) 143 | ), $uninitializedEnvironment); 144 | } 145 | } 146 | 147 | /** 148 | * Initializes context class and returns new context instance. 149 | * 150 | * @param Context $context 151 | */ 152 | private function initializeInstance(Context $context) 153 | { 154 | foreach ($this->contextInitializers as $initializer) { 155 | $initializer->initializeContext($context); 156 | } 157 | } 158 | 159 | /** 160 | * Registers context initializer. 161 | * 162 | * @param ContextInitializer $initializer 163 | */ 164 | public function registerContextInitializer(ContextInitializer $initializer) 165 | { 166 | $this->contextInitializers[] = $initializer; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/ServiceContainer/ContextServiceExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace FriendsOfBehat\ContextServiceExtension\ServiceContainer; 15 | 16 | use Behat\Behat\Context\ServiceContainer\ContextExtension; 17 | use Behat\Testwork\Environment\ServiceContainer\EnvironmentExtension; 18 | use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension; 19 | use Behat\Testwork\ServiceContainer\Extension; 20 | use Behat\Testwork\ServiceContainer\ExtensionManager; 21 | use FriendsOfBehat\ContextServiceExtension\Context\ContextRegistry; 22 | use FriendsOfBehat\ContextServiceExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler; 23 | use FriendsOfBehat\ContextServiceExtension\Listener\ScenarioContainerResetter; 24 | use FriendsOfBehat\ContextServiceExtension\ServiceContainer\Scenario\ContainerFactory; 25 | use FriendsOfBehat\ContextServiceExtension\ServiceContainer\Scenario\ContextRegistryPass; 26 | use FriendsOfBehat\CrossContainerExtension\CrossContainerProcessor; 27 | use FriendsOfBehat\CrossContainerExtension\ServiceContainer\CrossContainerExtension; 28 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 29 | use Symfony\Component\DependencyInjection\ContainerBuilder; 30 | use Symfony\Component\DependencyInjection\Definition; 31 | use Symfony\Component\DependencyInjection\Reference; 32 | 33 | /** 34 | * @internal 35 | */ 36 | final class ContextServiceExtension implements Extension 37 | { 38 | /** 39 | * @var CrossContainerProcessor|null 40 | */ 41 | private $crossContainerProcessor; 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getConfigKey(): string 47 | { 48 | return 'fob_context_service'; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function initialize(ExtensionManager $extensionManager): void 55 | { 56 | /** @var CrossContainerExtension|null $crossContainerExtension */ 57 | $crossContainerExtension = $extensionManager->getExtension('fob_cross_container'); 58 | if ($crossContainerExtension !== null) { 59 | $this->crossContainerProcessor = $crossContainerExtension->getCrossContainerProcessor(); 60 | } 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function configure(ArrayNodeDefinition $builder): void 67 | { 68 | $builder 69 | ->children() 70 | ->arrayNode('imports') 71 | ->performNoDeepMerging() 72 | ->prototype('scalar')->end() 73 | ->end() 74 | ->end() 75 | ; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function load(ContainerBuilder $container, array $config): void 82 | { 83 | $this->loadContextRegistry($container); 84 | $this->loadScenarioServiceContainer($container, $config); 85 | $this->loadEnvironmentHandler($container); 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function process(ContainerBuilder $container): void 92 | { 93 | /** @var ContainerBuilder $scenarioContainer */ 94 | $scenarioContainer = $container->get('fob_context_service.service_container.scenario'); 95 | 96 | if ($this->crossContainerProcessor !== null) { 97 | $this->crossContainerProcessor->process($scenarioContainer); 98 | } 99 | 100 | // This feature was introduced only in symfony/dependency-injection v3.3 101 | // So we are adding the feature for modern containers and leaving as-is for older ones 102 | if (method_exists($scenarioContainer, 'registerForAutoconfiguration')) { 103 | $scenarioContainer 104 | ->registerForAutoconfiguration(\Behat\Behat\Context\Context::class) 105 | ->addTag(ContextRegistryPass::CONTEXT_SERVICE_TAG) 106 | ; 107 | } 108 | 109 | $scenarioContainer->addCompilerPass(new ContextRegistryPass($container->getDefinition('fob_context_service.context_registry'))); 110 | $scenarioContainer->compile(); 111 | } 112 | 113 | /** 114 | * @param ContainerBuilder $container 115 | */ 116 | private function loadContextRegistry(ContainerBuilder $container): void 117 | { 118 | $container->setDefinition('fob_context_service.context_registry', (new Definition(ContextRegistry::class))->setPublic(false)); 119 | } 120 | 121 | /** 122 | * @param ContainerBuilder $container 123 | * @param array $config 124 | */ 125 | private function loadScenarioServiceContainer(ContainerBuilder $container, array $config): void 126 | { 127 | $container->set( 128 | 'fob_context_service.service_container.scenario', 129 | (new ContainerFactory())->createContainer($container->getParameter('paths.base'), $config['imports']) 130 | ); 131 | 132 | $definition = new Definition(ScenarioContainerResetter::class, [ 133 | new Reference('fob_context_service.service_container.scenario'), 134 | ]); 135 | $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG); 136 | $container->setDefinition('fob_context_service.service_container.scenario.resetter', $definition); 137 | } 138 | 139 | /** 140 | * @param ContainerBuilder $container 141 | */ 142 | private function loadEnvironmentHandler(ContainerBuilder $container): void 143 | { 144 | $definition = new Definition(ContextServiceEnvironmentHandler::class, [ 145 | new Reference('fob_context_service.service_container.scenario'), 146 | new Reference('fob_context_service.context_registry'), 147 | ]); 148 | $definition->addTag(EnvironmentExtension::HANDLER_TAG, ['priority' => 128]); 149 | 150 | foreach ($container->findTaggedServiceIds(ContextExtension::INITIALIZER_TAG) as $serviceId => $tags) { 151 | $definition->addMethodCall('registerContextInitializer', [$container->getDefinition($serviceId)]); 152 | } 153 | 154 | $container->setDefinition('fob_context_service.environment_handler.context_service', $definition); 155 | } 156 | } 157 | --------------------------------------------------------------------------------