Refactor our environment handler to decorate the original one

This commit is contained in:
Kamil Kokot
2019-02-13 00:38:07 +01:00
parent d74cd251d5
commit c54c581e74
7 changed files with 145 additions and 191 deletions

View File

@@ -13,21 +13,18 @@ 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\ContextFactory;
use Behat\Behat\Context\Environment\ContextEnvironment;
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
use Behat\Testwork\Environment\Environment;
use Behat\Testwork\Environment\Exception\EnvironmentIsolationException;
use Behat\Testwork\Environment\Handler\EnvironmentHandler;
use Behat\Testwork\Suite\Exception\SuiteConfigurationException;
use Behat\Testwork\Suite\GenericSuite;
use Behat\Testwork\Suite\Suite;
use FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle;
use FriendsOfBehat\SymfonyExtension\Context\Environment\InitialisedContextServiceEnvironment;
use FriendsOfBehat\SymfonyExtension\Context\Environment\UninitialisedContextServiceEnvironment;
use FriendsOfBehat\SymfonyExtension\Context\Environment\InitialisedSymfonyExtensionEnvironment;
use FriendsOfBehat\SymfonyExtension\Context\Environment\UninitialisedSymfonyExtensionEnvironment;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -36,29 +33,13 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
/** @var KernelInterface */
private $symfonyKernel;
/** @var ClassResolver[] */
private $classResolvers = [];
/** @var EnvironmentHandler */
private $decoratedEnvironmentHandler;
/** @var ContextFactory */
private $contextFactory;
/** @var ArgumentResolverFactory */
private $resolverFactory;
/**
* @param ArgumentResolverFactory|SuiteScopedResolverFactory $resolverFactory
*/
public function __construct(KernelInterface $symfonyKernel, ContextFactory $factory, $resolverFactory = null)
public function __construct(KernelInterface $symfonyKernel, EnvironmentHandler $decoratedEnvironmentHandler)
{
$this->symfonyKernel = $symfonyKernel;
$this->contextFactory = $factory;
if ($resolverFactory && !$resolverFactory instanceof ArgumentResolverFactory) {
$resolverFactory = new SuiteScopedResolverFactoryAdapter($resolverFactory);
}
$this->resolverFactory = $resolverFactory ?: new NullFactory();
$this->decoratedEnvironmentHandler = $decoratedEnvironmentHandler;
}
public function supportsSuite(Suite $suite): bool
@@ -68,21 +49,34 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
public function buildEnvironment(Suite $suite): Environment
{
$environment = new UninitialisedContextServiceEnvironment($suite);
foreach ($this->getSuiteContextsServices($suite) as [$contextId, $contextArguments]) {
$environment->registerContextService($contextId, $this->getContextClass($contextId), $contextArguments);
$symfonyContexts = [];
foreach ($this->getSuiteContextsServices($suite) as $serviceId) {
if (!$this->getContainer()->has($serviceId)) {
continue;
}
$symfonyContexts[$serviceId] = get_class($this->getContainer()->get($serviceId));
}
return $environment;
$delegatedSuite = $this->cloneSuiteWithoutContexts($suite, array_keys($symfonyContexts));
$delegatedEnvironment = $this->decoratedEnvironmentHandler->buildEnvironment($delegatedSuite);
if (!$delegatedEnvironment instanceof ContextEnvironment) {
throw new \Exception();
}
return new UninitialisedSymfonyExtensionEnvironment($suite, $symfonyContexts, $delegatedEnvironment);
}
public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null): bool
{
return $environment instanceof UninitialisedContextServiceEnvironment;
return $environment instanceof UninitialisedSymfonyExtensionEnvironment;
}
/**
* @param UninitialisedContextServiceEnvironment $uninitializedEnvironment
* @param UninitialisedSymfonyExtensionEnvironment $uninitializedEnvironment
*
* @throws EnvironmentIsolationException
*/
@@ -90,54 +84,78 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
{
$this->assertEnvironmentCanBeIsolated($uninitializedEnvironment, $testSubject);
$environment = new InitialisedContextServiceEnvironment($uninitializedEnvironment->getSuite());
$resolvers = $this->resolverFactory->createArgumentResolvers($environment);
$environment = new InitialisedSymfonyExtensionEnvironment($uninitializedEnvironment->getSuite());
foreach ($uninitializedEnvironment->getContextServicesWithArguments() as $contextId => $arguments) {
foreach ($uninitializedEnvironment->getServices() as $serviceId) {
/** @var Context $context */
$context = $this->getContext($contextId, $arguments, $resolvers);
$context = $this->getContainer()->get($serviceId);
$environment->registerContext($context);
}
$delegatedEnvironment = $this->decoratedEnvironmentHandler->isolateEnvironment($uninitializedEnvironment->getDelegatedEnvironment());
if (!$delegatedEnvironment instanceof InitializedContextEnvironment) {
throw new \Exception();
}
foreach ($delegatedEnvironment->getContexts() as $context) {
$environment->registerContext($context);
}
return $environment;
}
public function registerClassResolver(ClassResolver $classResolver): void
{
$this->classResolvers[] = $classResolver;
}
/**
* @return array[]
* @return string[]
*
* @throws SuiteConfigurationException If "contexts" setting is not an array
*/
private function getSuiteContextsServices(Suite $suite): array
{
$contextsServices = $suite->getSetting('contexts');
$contexts = $suite->getSetting('contexts');
if (!is_array($contextsServices)) {
if (!is_array($contexts)) {
throw new SuiteConfigurationException(sprintf(
'"contexts" setting of the "%s" suite is expected to be an array, %s given.',
$suite->getName(),
gettype($contextsServices)
gettype($contexts)
), $suite->getName());
}
return array_map(
function ($context): array {
$class = $context;
$arguments = [];
return array_map([$this, 'normaliseContext'], $contexts);
}
if (is_array($context)) {
$class = current(array_keys($context));
$arguments = $context[$class];
}
private function cloneSuiteWithoutContexts(Suite $suite, array $contextsToRemove): Suite
{
$contexts = $suite->getSetting('contexts');
return [$class, $arguments];
},
$contextsServices
);
if (!is_array($contexts)) {
throw new SuiteConfigurationException(sprintf(
'"contexts" setting of the "%s" suite is expected to be an array, %s given.',
$suite->getName(),
gettype($contexts)
), $suite->getName());
}
$contexts = array_filter($contexts, function ($context) use ($contextsToRemove): bool {
return !in_array($this->normaliseContext($context), $contextsToRemove, true);
});
return new GenericSuite($suite->getName(), array_merge($suite->getSettings(), ['contexts' => $contexts]));
}
private function normaliseContext($context): string
{
if (is_array($context)) {
return current(array_keys($context));
}
if (is_string($context)) {
return $context;
}
throw new \Exception();
}
/**
@@ -154,60 +172,6 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
}
}
private function resolveContextId(string $contextId): string
{
foreach ($this->classResolvers as $resolver) {
if ($resolver->supportsClass($contextId)) {
return $resolver->resolveClass($contextId);
}
}
return $contextId;
}
private function getContextClass(string $contextId): string
{
$contextId = $this->resolveContextId($contextId);
if ($this->getContainer()->has($contextId)) {
return get_class($this->getContainer()->get($contextId));
}
$class = '\\' . ltrim($contextId, '\\');
if (class_exists($class)) {
return $class;
}
throw new \DomainException(sprintf('There is no service or class "%s".', $contextId));
}
private function getContext(string $contextId, array $arguments = [], array $resolvers = []): Context
{
$contextId = $this->resolveContextId($contextId);
$class = '\\' . ltrim($contextId, '\\');
if ($this->getContainer()->has($contextId)) {
$context = $this->getContainer()->get($contextId);
} elseif (class_exists($class)) {
$context = $this->contextFactory->createContext($class, $arguments, $resolvers);
} else {
throw new \DomainException(sprintf('There is no service or class "%s".', $contextId));
}
if (!$context instanceof Context) {
throw new \DomainException(sprintf(
'Context "%s" referenced as "%s" needs to implement "%s".',
get_class($context),
$contextId,
Context::class
));
}
return $context;
}
private function getContainer(): ContainerInterface
{
try {

View File

@@ -22,7 +22,7 @@ use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEn
/**
* @see ContextServiceEnvironmentHandler
*/
final class InitialisedContextServiceEnvironment implements ContextServiceEnvironment
final class InitialisedSymfonyExtensionEnvironment implements SymfonyExtensionEnvironment
{
/** @var Suite */
private $suite;

View File

@@ -19,6 +19,6 @@ use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEn
/**
* @see ContextServiceEnvironmentHandler
*/
interface ContextServiceEnvironment extends ContextEnvironment
interface SymfonyExtensionEnvironment extends ContextEnvironment
{
}

View File

@@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
/*
* This file is part of the SymfonyExtension package.
*
* (c) Kamil Kokot <kamil@kokot.me>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FriendsOfBehat\SymfonyExtension\Context\Environment;
use Behat\Testwork\Environment\StaticEnvironment;
use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler;
/**
* @see ContextServiceEnvironmentHandler
*/
final class UninitialisedContextServiceEnvironment extends StaticEnvironment implements ContextServiceEnvironment
{
/** @var string[] */
private $contextServices = [];
public function registerContextService(string $serviceId, string $serviceClass, array $arguments = []): void
{
$this->contextServices[$serviceId] = [
'class' => $serviceClass,
'arguments' => $arguments,
];
}
public function getContextServices(): array
{
return array_keys($this->contextServices);
}
public function hasContexts(): bool
{
return count($this->contextServices) > 0;
}
public function getContextClasses(): array
{
return array_map(function (array $contextDetails): string {
return $contextDetails['class'];
}, $this->contextServices);
}
public function hasContextClass($class): bool
{
return in_array($class, $this->getContextClasses(), true);
}
public function getContextServicesWithArguments(): iterable
{
foreach ($this->contextServices as $contextDetails) {
yield $contextDetails['class'] => $contextDetails['arguments'];
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/*
* This file is part of the SymfonyExtension package.
*
* (c) Kamil Kokot <kamil@kokot.me>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FriendsOfBehat\SymfonyExtension\Context\Environment;
use Behat\Behat\Context\Environment\ContextEnvironment;
use Behat\Testwork\Environment\StaticEnvironment;
use Behat\Testwork\Suite\Suite;
use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler;
/**
* @see ContextServiceEnvironmentHandler
*/
final class UninitialisedSymfonyExtensionEnvironment extends StaticEnvironment implements SymfonyExtensionEnvironment
{
/** @var string[] */
private $contexts;
/** @var ContextEnvironment|null */
private $delegatedEnvironment;
public function __construct(Suite $suite, array $contexts, ContextEnvironment $delegatedEnvironment)
{
parent::__construct($suite);
$this->contexts = $contexts;
$this->delegatedEnvironment = $delegatedEnvironment;
}
public function getServices(): array
{
return array_keys($this->contexts);
}
public function hasContexts(): bool
{
return count($this->contexts) > 0 || $this->delegatedEnvironment->hasContexts();
}
public function getContextClasses(): array
{
return array_merge(array_values($this->contexts), $this->delegatedEnvironment->getContextClasses());
}
public function hasContextClass($class): bool
{
return in_array($class, $this->contexts, true) || $this->delegatedEnvironment->hasContextClass($class);
}
public function getDelegatedEnvironment(): ContextEnvironment
{
return $this->delegatedEnvironment;
}
}