Merge pull request #217 from loic425/fix/lazy-object-with-sf-7.3

Fix Mink lazy object with SF7.3 and PHP 8.4
This commit is contained in:
Łukasz Chruściel
2025-06-03 15:32:20 +02:00
committed by GitHub
14 changed files with 105 additions and 28 deletions

View File

@@ -17,15 +17,15 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
php-version: php-version:
- '8.1'
- '8.2' - '8.2'
- '8.3'
- '8.4'
symfony-version: symfony-version:
- '6.2.*'
- '6.4.*'
- '7.0.*' - '7.0.*'
exclude: - '7.3.*'
include:
- php-version: '8.1' - php-version: '8.1'
symfony-version: '7.0.*' symfony-version: '6.4.*'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3

1
.php-version Normal file
View File

@@ -0,0 +1 @@
8.3

View File

@@ -12,9 +12,9 @@
], ],
"require": { "require": {
"php": "^8.1", "php": "^8.1",
"behat/behat": "^3.6.1", "behat/behat": "^3.22",
"symfony/dependency-injection": "^6.2 || ^7.0", "symfony/dependency-injection": "^6.4 || ^7.0",
"symfony/http-kernel": "^6.2 || ^7.0" "symfony/http-kernel": "^6.4 || ^7.0"
}, },
"require-dev": { "require-dev": {
"behat/mink-browserkit-driver": "^2.0", "behat/mink-browserkit-driver": "^2.0",
@@ -24,11 +24,11 @@
"friends-of-behat/page-object-extension": "^0.3.2", "friends-of-behat/page-object-extension": "^0.3.2",
"friends-of-behat/service-container-extension": "^1.1", "friends-of-behat/service-container-extension": "^1.1",
"sylius-labs/coding-standard": ">=4.1.1, <=4.2.1", "sylius-labs/coding-standard": ">=4.1.1, <=4.2.1",
"symfony/browser-kit": "^6.2 || ^7.0", "symfony/browser-kit": "^6.4 || ^7.0",
"symfony/framework-bundle": "^6.2 || ^7.0", "symfony/framework-bundle": "^6.4 || ^7.0",
"symfony/process": "^6.2 || ^7.0", "symfony/process": "^6.4 || ^7.0",
"symfony/yaml": "^6.2 || ^7.0", "symfony/yaml": "^6.4 || ^7.0",
"vimeo/psalm": "4.30.0" "vimeo/psalm": "^6.0"
}, },
"suggest": { "suggest": {
"behat/mink-browserkit-driver": "^2.0", "behat/mink-browserkit-driver": "^2.0",

View File

@@ -14,24 +14,62 @@
</projectFiles> </projectFiles>
<issueHandlers> <issueHandlers>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="Symfony\Component\BrowserKit\AbstractBrowser" />
<referencedClass name="Symfony\Component\BrowserKit\Client" />
<referencedClass name="Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator" />
</errorLevel>
</UndefinedClass>
<InvalidAttribute> <InvalidAttribute>
<errorLevel type="suppress"> <errorLevel type="suppress">
<file name="src/Mink/MinkParameters.php" /> <file name="src/Mink/MinkParameters.php" />
</errorLevel> </errorLevel>
</InvalidAttribute> </InvalidAttribute>
<InvalidOperand>
<errorLevel type="suppress">
<file name="src/ServiceContainer/SymfonyExtension.php" />
</errorLevel>
</InvalidOperand>
<InvalidReturnStatement>
<errorLevel type="suppress">
<file name="src/Context/Environment/InitializedSymfonyExtensionEnvironment.php" />
</errorLevel>
</InvalidReturnStatement>
<InvalidReturnType>
<errorLevel type="suppress">
<file name="src/Context/Environment/InitializedSymfonyExtensionEnvironment.php" />
</errorLevel>
</InvalidReturnType>
<MissingTemplateParam>
<errorLevel type="suppress">
<file name="src/Mink/MinkParameters.php" />
</errorLevel>
</MissingTemplateParam>
<!-- Workaround for https://github.com/vimeo/psalm/issues/7026 --> <!-- Workaround for https://github.com/vimeo/psalm/issues/7026 -->
<ReservedWord> <ReservedWord>
<errorLevel type="suppress"> <errorLevel type="suppress">
<directory name="src" /> <directory name="src" />
</errorLevel> </errorLevel>
</ReservedWord> </ReservedWord>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="Symfony\Component\BrowserKit\AbstractBrowser" />
<referencedClass name="Symfony\Component\BrowserKit\Client" />
<referencedClass name="Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator" />
</errorLevel>
</UndefinedClass>
<UndefinedInterfaceMethod>
<errorLevel type="suppress">
<file name="src/ServiceContainer/SymfonyExtension.php" />
</errorLevel>
</UndefinedInterfaceMethod>
<UnusedForeachValue>
<errorLevel type="suppress">
<file name="src/Bundle/DependencyInjection/FriendsOfBehatSymfonyExtensionExtension.php" />
<file name="src/ServiceContainer/SymfonyExtension.php" />
</errorLevel>
</UnusedForeachValue>
</issueHandlers> </issueHandlers>
</psalm> </psalm>

View File

@@ -22,6 +22,7 @@ use Symfony\Component\HttpKernel\KernelInterface;
final class FriendsOfBehatSymfonyExtensionExtension extends Extension implements CompilerPassInterface final class FriendsOfBehatSymfonyExtensionExtension extends Extension implements CompilerPassInterface
{ {
#[\Override]
public function load(array $configs, ContainerBuilder $container): void public function load(array $configs, ContainerBuilder $container): void
{ {
$this->provideMinkIntegration($container); $this->provideMinkIntegration($container);
@@ -31,6 +32,7 @@ final class FriendsOfBehatSymfonyExtensionExtension extends Extension implements
$container->registerForAutoconfiguration(Context::class)->addTag('fob.context'); $container->registerForAutoconfiguration(Context::class)->addTag('fob.context');
} }
#[\Override]
public function process(ContainerBuilder $container): void public function process(ContainerBuilder $container): void
{ {
$this->provideBrowserKitIntegration($container); $this->provideBrowserKitIntegration($container);

View File

@@ -51,11 +51,13 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
$this->contextInitializers[] = $contextInitializer; $this->contextInitializers[] = $contextInitializer;
} }
#[\Override]
public function supportsSuite(Suite $suite): bool public function supportsSuite(Suite $suite): bool
{ {
return $suite->hasSetting('contexts'); return $suite->hasSetting('contexts');
} }
#[\Override]
public function buildEnvironment(Suite $suite): Environment public function buildEnvironment(Suite $suite): Environment
{ {
$symfonyContexts = []; $symfonyContexts = [];
@@ -65,7 +67,6 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
continue; continue;
} }
/** @var object $service */
$service = $this->getContainer()->get($serviceId); $service = $this->getContainer()->get($serviceId);
$symfonyContexts[$serviceId] = get_class($service); $symfonyContexts[$serviceId] = get_class($service);
@@ -79,6 +80,7 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
return new UninitializedSymfonyExtensionEnvironment($suite, $symfonyContexts, $delegatedEnvironment); return new UninitializedSymfonyExtensionEnvironment($suite, $symfonyContexts, $delegatedEnvironment);
} }
#[\Override]
public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null): bool public function supportsEnvironmentAndSubject(Environment $environment, $testSubject = null): bool
{ {
return $environment instanceof UninitializedSymfonyExtensionEnvironment; return $environment instanceof UninitializedSymfonyExtensionEnvironment;
@@ -87,6 +89,7 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
/** /**
* @throws EnvironmentIsolationException * @throws EnvironmentIsolationException
*/ */
#[\Override]
public function isolateEnvironment(Environment $environment, $testSubject = null): Environment public function isolateEnvironment(Environment $environment, $testSubject = null): Environment
{ {
$this->assertEnvironmentCanBeIsolated($environment, $testSubject); $this->assertEnvironmentCanBeIsolated($environment, $testSubject);
@@ -158,7 +161,12 @@ final class ContextServiceEnvironmentHandler implements EnvironmentHandler
return new GenericSuite($suite->getName(), array_merge($suite->getSettings(), ['contexts' => $contexts])); return new GenericSuite($suite->getName(), array_merge($suite->getSettings(), ['contexts' => $contexts]));
} }
private function normalizeContext($context): string /**
* @return (int|string)|false
*
* @psalm-return array-key|false
*/
private function normalizeContext($context)
{ {
if (is_array($context)) { if (is_array($context)) {
return current(array_keys($context)); return current(array_keys($context));

View File

@@ -43,11 +43,13 @@ final class InitializedSymfonyExtensionEnvironment implements SymfonyExtensionEn
$this->contexts[get_class($context)] = $context; $this->contexts[get_class($context)] = $context;
} }
#[\Override]
public function getSuite(): Suite public function getSuite(): Suite
{ {
return $this->suite; return $this->suite;
} }
#[\Override]
public function bindCallee(Callee $callee): callable public function bindCallee(Callee $callee): callable
{ {
$callable = $callee->getCallable(); $callable = $callee->getCallable();
@@ -59,16 +61,24 @@ final class InitializedSymfonyExtensionEnvironment implements SymfonyExtensionEn
return $callable; return $callable;
} }
#[\Override]
public function hasContexts(): bool public function hasContexts(): bool
{ {
return count($this->contexts) > 0; return count($this->contexts) > 0;
} }
#[\Override]
/**
* @return key-of<TArray>[]
*
* @psalm-return list<key-of<class-string-map<T as Behat\Behat\Context\Context, T:class-string-map as Behat\Behat\Context\Context>>>
*/
public function getContextClasses(): array public function getContextClasses(): array
{ {
return array_keys($this->contexts); return array_keys($this->contexts);
} }
#[\Override]
public function hasContextClass($class): bool public function hasContextClass($class): bool
{ {
return isset($this->contexts[$class]); return isset($this->contexts[$class]);

View File

@@ -42,16 +42,19 @@ final class UninitializedSymfonyExtensionEnvironment extends StaticEnvironment i
return array_keys($this->contexts); return array_keys($this->contexts);
} }
#[\Override]
public function hasContexts(): bool public function hasContexts(): bool
{ {
return count($this->contexts) > 0 || $this->delegatedEnvironment->hasContexts(); return count($this->contexts) > 0 || $this->delegatedEnvironment->hasContexts();
} }
#[\Override]
public function getContextClasses(): array public function getContextClasses(): array
{ {
return array_merge(array_values($this->contexts), $this->delegatedEnvironment->getContextClasses()); return array_merge(array_values($this->contexts), $this->delegatedEnvironment->getContextClasses());
} }
#[\Override]
public function hasContextClass($class): bool public function hasContextClass($class): bool
{ {
return in_array($class, $this->contexts, true) || $this->delegatedEnvironment->hasContextClass($class); return in_array($class, $this->contexts, true) || $this->delegatedEnvironment->hasContextClass($class);

View File

@@ -25,20 +25,24 @@ final class SymfonyDriverFactory implements DriverFactory
$this->kernel = $kernel; $this->kernel = $kernel;
} }
#[\Override]
public function getDriverName(): string public function getDriverName(): string
{ {
return $this->name; return $this->name;
} }
#[\Override]
public function supportsJavascript(): bool public function supportsJavascript(): bool
{ {
return false; return false;
} }
#[\Override]
public function configure(ArrayNodeDefinition $builder): void public function configure(ArrayNodeDefinition $builder): void
{ {
} }
#[\Override]
public function buildDriver(array $config): Definition public function buildDriver(array $config): Definition
{ {
if (!class_exists(BrowserKitDriver::class)) { if (!class_exists(BrowserKitDriver::class)) {

View File

@@ -34,6 +34,7 @@ final class SymfonyDriver extends BrowserKitDriver
parent::__construct($this->createBrowser(), $this->baseUrl); parent::__construct($this->createBrowser(), $this->baseUrl);
} }
#[\Override]
public function reset() public function reset()
{ {
parent::reset(); parent::reset();
@@ -61,7 +62,6 @@ final class SymfonyDriver extends BrowserKitDriver
private function createBrowser(): AbstractBrowser private function createBrowser(): AbstractBrowser
{ {
/** @var object $testClient */
$testClient = $this->kernel->getContainer()->get('test.client'); $testClient = $this->kernel->getContainer()->get('test.client');
if (!$testClient instanceof AbstractBrowser) { if (!$testClient instanceof AbstractBrowser) {

View File

@@ -28,6 +28,7 @@ final class KernelOrchestrator implements EventSubscriberInterface
$this->behatContainer = $behatContainer; $this->behatContainer = $behatContainer;
} }
#[\Override]
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
return [ return [

View File

@@ -6,6 +6,9 @@ namespace FriendsOfBehat\SymfonyExtension\Mink;
use Behat\Mink\Mink as BaseMink; use Behat\Mink\Mink as BaseMink;
/**
* @deprecated use Behat\Mink\Mink instead, it will be removed on 3.0.
*/
class Mink extends BaseMink class Mink extends BaseMink
{ {
/** /**
@@ -15,6 +18,7 @@ class Mink extends BaseMink
* in an invalid state. Therefore, not stopping all the sessions while destructing Mink * in an invalid state. Therefore, not stopping all the sessions while destructing Mink
* saves our sanity. * saves our sanity.
*/ */
#[\Override]
public function __destruct() public function __destruct()
{ {
// Intentionally left empty // Intentionally left empty

View File

@@ -17,11 +17,13 @@ class MinkParameters implements \Countable, \IteratorAggregate, \ArrayAccess
$this->minkParameters = $minkParameters; $this->minkParameters = $minkParameters;
} }
#[\Override]
public function getIterator(): \Traversable public function getIterator(): \Traversable
{ {
return new \ArrayIterator($this->minkParameters); return new \ArrayIterator($this->minkParameters);
} }
#[\Override]
public function offsetExists($offset): bool public function offsetExists($offset): bool
{ {
return array_key_exists($offset, $this->minkParameters); return array_key_exists($offset, $this->minkParameters);
@@ -30,22 +32,26 @@ class MinkParameters implements \Countable, \IteratorAggregate, \ArrayAccess
/** /**
* @return mixed * @return mixed
*/ */
#[\Override]
#[ReturnTypeWillChange] #[ReturnTypeWillChange]
public function offsetGet($offset) public function offsetGet($offset)
{ {
return $this->minkParameters[$offset] ?? null; return $this->minkParameters[$offset] ?? null;
} }
#[\Override]
public function offsetSet($offset, $value): void public function offsetSet($offset, $value): void
{ {
throw new \BadMethodCallException(sprintf('"%s" is immutable.', self::class)); throw new \BadMethodCallException(sprintf('"%s" is immutable.', self::class));
} }
#[\Override]
public function offsetUnset($offset): void public function offsetUnset($offset): void
{ {
throw new \BadMethodCallException(sprintf('"%s" is immutable.', self::class)); throw new \BadMethodCallException(sprintf('"%s" is immutable.', self::class));
} }
#[\Override]
public function count(): int public function count(): int
{ {
return count($this->minkParameters); return count($this->minkParameters);

View File

@@ -14,7 +14,6 @@ use Behat\Testwork\ServiceContainer\ExtensionManager;
use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler; use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler;
use FriendsOfBehat\SymfonyExtension\Driver\Factory\SymfonyDriverFactory; use FriendsOfBehat\SymfonyExtension\Driver\Factory\SymfonyDriverFactory;
use FriendsOfBehat\SymfonyExtension\Listener\KernelOrchestrator; use FriendsOfBehat\SymfonyExtension\Listener\KernelOrchestrator;
use FriendsOfBehat\SymfonyExtension\Mink\Mink;
use FriendsOfBehat\SymfonyExtension\Mink\MinkParameters; use FriendsOfBehat\SymfonyExtension\Mink\MinkParameters;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
@@ -41,16 +40,19 @@ final class SymfonyExtension implements Extension
/** @var bool */ /** @var bool */
private $minkExtensionFound = false; private $minkExtensionFound = false;
#[\Override]
public function getConfigKey(): string public function getConfigKey(): string
{ {
return 'fob_symfony'; return 'fob_symfony';
} }
#[\Override]
public function initialize(ExtensionManager $extensionManager): void public function initialize(ExtensionManager $extensionManager): void
{ {
$this->registerMinkDriver($extensionManager); $this->registerMinkDriver($extensionManager);
} }
#[\Override]
public function configure(ArrayNodeDefinition $builder): void public function configure(ArrayNodeDefinition $builder): void
{ {
$builder $builder
@@ -70,6 +72,7 @@ final class SymfonyExtension implements Extension
; ;
} }
#[\Override]
public function load(ContainerBuilder $container, array $config): void public function load(ContainerBuilder $container, array $config): void
{ {
$this->setupTestEnvironment($config['kernel']['environment'] ?? 'test'); $this->setupTestEnvironment($config['kernel']['environment'] ?? 'test');
@@ -90,13 +93,10 @@ final class SymfonyExtension implements Extension
} }
} }
#[\Override]
public function process(ContainerBuilder $container): void public function process(ContainerBuilder $container): void
{ {
$this->processEnvironmentHandler($container); $this->processEnvironmentHandler($container);
if ($this->minkExtensionFound) {
$container->getDefinition(MinkExtension::MINK_ID)->setClass(Mink::class);
}
} }
private function registerMinkDriver(ExtensionManager $extensionManager): void private function registerMinkDriver(ExtensionManager $extensionManager): void