Skip to content

Module code adapted for PHPStan #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
tools: composer:v2, phpstan
extensions: ctype, iconv, intl, json, mbstring, pdo, pdo_sqlite
coverage: none

Expand Down Expand Up @@ -56,39 +56,38 @@ jobs:
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json', 'composer.lock') }}
key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.{json,lock}') }}
restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer-

- name: Install PHPUnit 10
run: composer require --dev --no-update "phpunit/phpunit=^10.0"
run: composer require --dev --no-update phpunit/phpunit:^10.0

- name: Install dependencies
env:
MATRIX_SYMFONY: ${{ matrix.symfony }}
run: |
composer require symfony/finder=${{ env.COMP_SYMFONY }} --no-update
composer require symfony/yaml=${{ env.COMP_SYMFONY }} --no-update
composer require symfony/console=${{ env.COMP_SYMFONY }} --no-update
composer require symfony/event-dispatcher=${{ env.COMP_SYMFONY }} --no-update
composer require symfony/css-selector=${{ env.COMP_SYMFONY }} --no-update
composer require symfony/dom-crawler=${{ env.COMP_SYMFONY }} --no-update
composer require symfony/browser-kit=${{ env.COMP_SYMFONY }} --no-update
composer require vlucas/phpdotenv --no-update
composer require codeception/module-asserts="3.*" --no-update
composer require codeception/module-doctrine="3.*" --no-update
composer require --no-update \
symfony/{finder,yaml,console,event-dispatcher,css-selector,dom-crawler,browser-kit}:${{ env.COMP_SYMFONY }} \
vlucas/phpdotenv \
codeception/module-asserts:"3.*" \
codeception/module-doctrine:"3.*"

if [[ "$MATRIX_SYMFONY" == "6.4wApi" ]]; then
composer require codeception/module-rest="3.*" --no-update
fi

composer update --prefer-dist --no-progress --no-dev
composer update --prefer-dist --no-progress

- name: Run PHPStan (max)
if: ${{ matrix.symfony == '7.2.*' }}
run: phpstan analyse src --level=max --no-progress --error-format=github --memory-limit=1G

- name: Validate Composer files
run: composer validate --strict
working-directory: framework-tests

- name: Install PHPUnit in framework-tests
run: composer require --dev --no-update "phpunit/phpunit=^10.0"
run: composer require --dev --no-update phpunit/phpunit:^10.0
working-directory: framework-tests

- name: Prepare Symfony sample
Expand Down
110 changes: 56 additions & 54 deletions src/Codeception/Lib/Connector/Symfony.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,44 @@
namespace Codeception\Lib\Connector;

use InvalidArgumentException;
use ReflectionClass;
use LogicException;
use ReflectionMethod;
use ReflectionProperty;
use Symfony\Bundle\FrameworkBundle\Test\TestContainer;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelBrowser;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use function array_keys;

use function codecept_debug;

/**
* @property KernelInterface $kernel
*/
class Symfony extends HttpKernelBrowser
{
private ContainerInterface $container;
private bool $hasPerformedRequest = false;
private ?ContainerInterface $container;

public function __construct(
Kernel $kernel,
HttpKernelInterface $kernel,
/** @var array<non-empty-string, object> */
public array $persistentServices = [],
private readonly bool $rebootable = true
private bool $reboot = true
) {
parent::__construct($kernel);
$this->followRedirects();
$this->container = $this->getContainer();
$this->container = $this->resolveContainer();
$this->rebootKernel();
}

/** @param Request $request */
protected function doRequest(object $request): Response
{
if ($this->rebootable) {
if ($this->hasPerformedRequest) {
$this->rebootKernel();
} else {
$this->hasPerformedRequest = true;
}
if ($this->reboot) {
$this->hasPerformedRequest ? $this->rebootKernel() : $this->hasPerformedRequest = true;
}

return parent::doRequest($request);
Expand All @@ -57,30 +57,27 @@ protected function doRequest(object $request): Response
*/
public function rebootKernel(): void
{
if ($this->container) {
foreach (array_keys($this->persistentServices) as $serviceName) {
if ($service = $this->getService($serviceName)) {
$this->persistentServices[$serviceName] = $service;
}
foreach (array_keys($this->persistentServices) as $service) {
if ($this->container->has($service)) {
$this->persistentServices[$service] = $this->container->get($service);
}
}

$this->persistDoctrineConnections();
$this->ensureKernelShutdown();
$this->kernel->boot();
$this->container = $this->getContainer();

foreach ($this->persistentServices as $serviceName => $service) {
if ($this->kernel instanceof Kernel) {
$this->ensureKernelShutdown();
$this->kernel->boot();
}
$this->container = $this->resolveContainer();
foreach ($this->persistentServices as $name => $service) {
try {
$this->container->set($serviceName, $service);
$this->container->set($name, $service);
} catch (InvalidArgumentException $e) {
codecept_debug("[Symfony] Can't set persistent service {$serviceName}: " . $e->getMessage());
codecept_debug("[Symfony] Can't set persistent service {$name}: {$e->getMessage()}");
}
}

if ($profiler = $this->getProfiler()) {
$profiler->enable();
}
$this->getProfiler()?->enable();
}

protected function ensureKernelShutdown(): void
Expand All @@ -89,27 +86,25 @@ protected function ensureKernelShutdown(): void
$this->kernel->shutdown();
}

private function getContainer(): ?ContainerInterface
private function resolveContainer(): ContainerInterface
{
/** @var ContainerInterface $container */
$container = $this->kernel->getContainer();
return $container->has('test.service_container')
? $container->get('test.service_container')
: $container;
}

private function getProfiler(): ?Profiler
{
return $this->container->has('profiler')
? $this->container->get('profiler')
: null;
if ($container->has('test.service_container')) {
$testContainer = $container->get('test.service_container');
if (!$testContainer instanceof ContainerInterface) {
throw new LogicException('Service "test.service_container" must implement ' . ContainerInterface::class);
}
$container = $testContainer;
}

return $container;
}

private function getService(string $serviceName): ?object
private function getProfiler(): ?Profiler
{
return $this->container->has($serviceName)
? $this->container->get($serviceName)
: null;
$profiler = $this->container->get('profiler');
return $profiler instanceof Profiler ? $profiler : null;
}

private function persistDoctrineConnections(): void
Expand All @@ -119,20 +114,27 @@ private function persistDoctrineConnections(): void
}

if ($this->container instanceof TestContainer) {
$reflectedTestContainer = new ReflectionMethod($this->container, 'getPublicContainer');
$reflectedTestContainer->setAccessible(true);
$publicContainer = $reflectedTestContainer->invoke($this->container);
$method = new ReflectionMethod($this->container, 'getPublicContainer');
$publicContainer = $method->invoke($this->container);
} else {
$publicContainer = $this->container;
}

$reflectedContainer = new ReflectionClass($publicContainer);
$reflectionTarget = $reflectedContainer->hasProperty('parameters') ? $publicContainer : $publicContainer->getParameterBag();
if (!is_object($publicContainer) || !method_exists($publicContainer, 'getParameterBag')) {
return;
}

$target = property_exists($publicContainer, 'parameters')
? $publicContainer
: $publicContainer->getParameterBag();

if (!is_object($target) || !property_exists($target, 'parameters')) {
return;
}
$prop = new ReflectionProperty($target, 'parameters');

$reflectedParameters = new ReflectionProperty($reflectionTarget, 'parameters');
$reflectedParameters->setAccessible(true);
$parameters = $reflectedParameters->getValue($reflectionTarget);
unset($parameters['doctrine.connections']);
$reflectedParameters->setValue($reflectionTarget, $parameters);
$params = (array) $prop->getValue($target);
unset($params['doctrine.connections']);
$prop->setValue($target, $params);
}
}
Loading