diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2492a31..7e7c9e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - php: [8.0, 8.1, 8.2, 8.3, 8.4] + php: [8.3, 8.4] steps: - name: Checkout code diff --git a/composer.json b/composer.json index 8cb1f78..f93903b 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ ], "minimum-stability": "RC", "require": { - "php": "^8.0", + "php": "^8.3", "codeception/codeception": "^5.0.8", "codeception/lib-innerbrowser": "^3.0 | ^4.0" }, @@ -29,8 +29,8 @@ "codemix/yii2-localeurls": "^1.7", "codeception/module-asserts": ">= 3.0", "codeception/module-filesystem": "> 3.0", - "phpstan/phpstan": "^1.10", - "rector/rector": "^1.2" + "phpstan/phpstan": "^2", + "rector/rector": "^2" }, "autoload":{ "classmap": ["src/"] diff --git a/phpstan.neon b/phpstan.neon index 7019dd5..9b7e02f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,7 +5,7 @@ parameters: dynamicConstantNames: - CONSOLE - YII_DEBUG - level: 5 + level: 9 paths: - src checkMaybeUndefinedVariables: true diff --git a/src/Codeception/Lib/Connector/Yii2.php b/src/Codeception/Lib/Connector/Yii2.php index b7df1d5..d6f89cf 100644 --- a/src/Codeception/Lib/Connector/Yii2.php +++ b/src/Codeception/Lib/Connector/Yii2.php @@ -5,6 +5,7 @@ namespace Codeception\Lib\Connector; use Codeception\Exception\ConfigurationException; +use Codeception\Exception\ModuleConfigException; use Codeception\Lib\Connector\Yii2\Logger; use Codeception\Lib\Connector\Yii2\TestMailer; use Codeception\Util\Debug; @@ -13,11 +14,16 @@ use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\CookieJar; use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request as BrowserkitRequest; +use yii\web\Request as YiiRequest; use Symfony\Component\BrowserKit\Response; use Yii; +use yii\base\Component; +use yii\base\Event; use yii\base\ExitException; use yii\base\Security; use yii\base\UserException; +use yii\mail\BaseMessage; use yii\mail\MessageInterface; use yii\web\Application; use yii\web\ErrorHandler; @@ -26,6 +32,10 @@ use yii\web\Response as YiiResponse; use yii\web\User; + +/** + * @extends Client + */ class Yii2 extends Client { use Shared\PhpSuperGlobalsConverter; @@ -98,18 +108,29 @@ class Yii2 extends Client public string|null $applicationClass = null; + /** + * @var list + */ private array $emails = []; /** - * @deprecated since 2.5, will become protected in 3.0. Directly access to \Yii::$app if you need to interact with it. * @internal */ - public function getApplication(): \yii\base\Application + protected function getApplication(): \yii\base\Application { if (!isset(Yii::$app)) { $this->startApp(); } - return Yii::$app; + return Yii::$app ?? throw new \RuntimeException('Failed to create Yii2 application'); + } + + private function getWebRequest(): YiiRequest + { + $request = $this->getApplication()->request; + if (!$request instanceof YiiRequest) { + throw new \RuntimeException('Request component is not of type ' . YiiRequest::class); + } + return $request; } public function resetApplication(bool $closeSession = true): void @@ -120,9 +141,7 @@ public function resetApplication(bool $closeSession = true): void } Yii::$app = null; \yii\web\UploadedFile::reset(); - if (method_exists(\yii\base\Event::class, 'offAll')) { - \yii\base\Event::offAll(); - } + Event::offAll(); Yii::setLogger(null); // This resolves an issue with database connections not closing properly. gc_collect_cycles(); @@ -161,23 +180,23 @@ public function findAndLoginUser(int|string|IdentityInterface $user): void * @param string $value The value of the cookie * @return string The value to send to the browser */ - public function hashCookieData($name, $value): string + public function hashCookieData(string $name, string $value): string { - $app = $this->getApplication(); - if (!$app->request->enableCookieValidation) { + $request = $this->getWebRequest(); + if (!$request->enableCookieValidation) { return $value; } - return $app->security->hashData(serialize([$name, $value]), $app->request->cookieValidationKey); + return $this->getApplication()->security->hashData(serialize([$name, $value]), $request->cookieValidationKey); } /** * @internal - * @return array List of regex patterns for recognized domain names + * @return non-empty-list List of regex patterns for recognized domain names */ public function getInternalDomains(): array { - /** @var \yii\web\UrlManager $urlManager */ $urlManager = $this->getApplication()->urlManager; + $domains = [$this->getDomainRegex($urlManager->hostInfo)]; if ($urlManager->enablePrettyUrl) { foreach ($urlManager->rules as $rule) { @@ -187,12 +206,12 @@ public function getInternalDomains(): array } } } - return array_unique($domains); + return array_values(array_unique($domains)); } /** * @internal - * @return array List of sent emails + * @return list List of sent emails */ public function getEmails(): array { @@ -211,13 +230,14 @@ public function clearEmails(): void /** * @internal */ - public function getComponent($name) + public function getComponent(string $name): object|null { $app = $this->getApplication(); - if (!$app->has($name)) { + $result = $app->get($name, false); + if (!isset($result)) { throw new ConfigurationException("Component $name is not available in current application"); } - return $app->get($name); + return $result; } /** @@ -240,6 +260,9 @@ function ($matches) use (&$parameters): string { $template ); } + if ($template === null) { + throw new \RuntimeException("Failed to parse domain regex"); + } $template = preg_quote($template); $template = strtr($template, $parameters); return '/^' . $template . '$/u'; @@ -251,7 +274,7 @@ function ($matches) use (&$parameters): string { */ public function getCsrfParamName(): string { - return $this->getApplication()->request->csrfParam; + return $this->getWebRequest()->csrfParam; } public function startApp(?\yii\log\Logger $logger = null): void @@ -268,7 +291,11 @@ public function startApp(?\yii\log\Logger $logger = null): void } $config = $this->mockMailer($config); - Yii::$app = Yii::createObject($config); + $app = Yii::createObject($config); + if (!$app instanceof \yii\base\Application) { + throw new ModuleConfigException($this, "Failed to initialize Yii2 app"); + } + \Yii::$app = $app; if ($logger instanceof \yii\log\Logger) { Yii::setLogger($logger); @@ -278,9 +305,9 @@ public function startApp(?\yii\log\Logger $logger = null): void } /** - * @param \Symfony\Component\BrowserKit\Request $request + * @param BrowserkitRequest $request */ - public function doRequest(object $request): \Symfony\Component\BrowserKit\Response + public function doRequest(object $request): Response { $_COOKIE = $request->getCookies(); $_SERVER = $request->getServer(); @@ -337,9 +364,9 @@ public function doRequest(object $request): \Symfony\Component\BrowserKit\Respon * Sending the response is problematic because it tries to send headers. */ $app->trigger($app::EVENT_BEFORE_REQUEST); - $response = $app->handleRequest($yiiRequest); + $yiiResponse = $app->handleRequest($yiiRequest); $app->trigger($app::EVENT_AFTER_REQUEST); - $response->send(); + $yiiResponse->send(); } catch (\Exception $e) { if ($e instanceof UserException) { // Don't discard output and pass exception handling to Yii to be able @@ -350,37 +377,32 @@ public function doRequest(object $request): \Symfony\Component\BrowserKit\Respon // for exceptions not related to Http, we pass them to Codeception throw $e; } - $response = $app->response; + $yiiResponse = $app->response; } - $this->encodeCookies($response, $yiiRequest, $app->security); + $this->encodeCookies($yiiResponse, $yiiRequest, $app->security); - if ($response->isRedirection) { - Debug::debug("[Redirect with headers]" . print_r($response->getHeaders()->toArray(), true)); + if ($yiiResponse->isRedirection) { + Debug::debug("[Redirect with headers]" . print_r($yiiResponse->getHeaders()->toArray(), true)); } $content = ob_get_clean(); - if (empty($content) && !empty($response->content) && !isset($response->stream)) { - throw new \Exception('No content was sent from Yii application'); + if (empty($content) && !empty($yiiResponse->content) && !isset($yiiResponse->stream)) { + throw new \RuntimeException('No content was sent from Yii application'); + } elseif ($content === false) { + throw new \RuntimeException('Failed to get output buffer'); } - return new Response($content, $response->statusCode, $response->getHeaders()->toArray()); - } - - protected function revertErrorHandler() - { - $handler = new ErrorHandler(); - set_error_handler([$handler, 'errorHandler']); + return new Response($content, $yiiResponse->statusCode, $yiiResponse->getHeaders()->toArray()); } - /** * Encodes the cookies and adds them to the headers. * @throws \yii\base\InvalidConfigException */ protected function encodeCookies( YiiResponse $response, - Request $request, + YiiRequest $request, Security $security ): void { if ($request->enableCookieValidation) { @@ -433,11 +455,19 @@ protected function mockMailer(array $config): array $mailerConfig = [ 'class' => TestMailer::class, - 'callback' => function (MessageInterface $message): void { + 'callback' => function (BaseMessage $message): void { $this->emails[] = $message; } ]; + if (isset($config['components'])) { + if (!is_array($config['components'])) { + throw new ModuleConfigException($this, + "Yii2 config does not contain components key is not of type array"); + } + } else { + $config['components'] = []; + } if (isset($config['components']['mailer']) && is_array($config['components']['mailer'])) { foreach ($config['components']['mailer'] as $name => $value) { if (in_array($name, $allowedOptions, true)) { @@ -487,7 +517,7 @@ public function setContext(array $context): void */ public function closeSession(): void { - $app = \Yii::$app; + $app = $this->getApplication(); if ($app instanceof \yii\web\Application && $app->has('session', true)) { $app->session->close(); } @@ -511,8 +541,8 @@ protected function resetResponse(Application $app): void Debug::debug(<<|JsonSerializable $message + * @return void + */ protected function debug(string|array|JsonSerializable $message): void { $title = (new ReflectionClass($this))->getShortName(); if (is_array($message) || is_object($message)) { - $message = stripslashes(json_encode($message)); + $message = stripslashes(json_encode($message, JSON_THROW_ON_ERROR)); } codecept_debug("[$title] $message"); } diff --git a/src/Codeception/Lib/Connector/Yii2/Logger.php b/src/Codeception/Lib/Connector/Yii2/Logger.php index 820e4ba..cae874f 100644 --- a/src/Codeception/Lib/Connector/Yii2/Logger.php +++ b/src/Codeception/Lib/Connector/Yii2/Logger.php @@ -11,9 +11,16 @@ class Logger extends YiiLogger { + /** + * @var \SplQueue + */ private \SplQueue $logQueue; - public function __construct(private int $maxLogItems = 5, array $config = []) + /** + * @param int $maxLogItems + * @param array $config + */ + public function __construct(private readonly int $maxLogItems = 5, array $config = []) { parent::__construct($config); $this->logQueue = new \SplQueue(); @@ -25,7 +32,7 @@ public function init(): void } /** - * @param string|array|YiiException $message + * @param string|array|YiiException $message * @param self::LEVEL_INFO|self::LEVEL_WARNING|self::LEVEL_ERROR $level * @param string $category */ diff --git a/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php b/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php index 350411a..ae9f19f 100644 --- a/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php +++ b/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php @@ -15,8 +15,17 @@ */ class TransactionForcer extends ConnectionWatcher { + /** + * @var array + */ private array $pdoCache = []; + /** + * @var array + */ private array $dsnCache = []; + /** + * @var array + */ private array $transactions = []; public function __construct(private bool $ignoreCollidingDSN) @@ -40,13 +49,13 @@ protected function connectionOpened(Connection $connection): void 'attributes' => $connection->attributes, 'emulatePrepare' => $connection->emulatePrepare, 'charset' => $connection->charset, - ])); + ], JSON_THROW_ON_ERROR)); /* * If keys match we assume connections are "similar enough". */ if (isset($this->pdoCache[$key])) { $connection->pdo = $this->pdoCache[$key]; - } else { + } elseif(isset($connection->pdo)) { $this->pdoCache[$key] = $connection->pdo; } if (isset($this->dsnCache[$connection->dsn]) diff --git a/src/Codeception/Module/Yii2.php b/src/Codeception/Module/Yii2.php index fdd47b8..cfa9eee 100644 --- a/src/Codeception/Module/Yii2.php +++ b/src/Codeception/Module/Yii2.php @@ -18,11 +18,17 @@ use Codeception\TestInterface; use ReflectionClass; use RuntimeException; -use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; use Yii; use yii\base\Security; +use yii\web\Application as WebApplication; use yii\db\ActiveQueryInterface; +use yii\db\ActiveRecordInterface; use yii\helpers\Url; +use yii\mail\BaseMessage; +use yii\mail\MessageInterface; +use yii\test\Fixture; use yii\web\Application; use yii\web\IdentityInterface; @@ -170,17 +176,65 @@ * Maintainer: **samdark** * Stability: **stable** * + * @phpstan-type ModuleConfig array{ + * fixturesMethod: string, + * cleanup: bool, + * ignoreCollidingDSN: bool, + * transaction: bool|null, + * entryScript: string, + * entryUrl: string, + * configFile: string|null, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null + * } + * + * @phpstan-type ValidConfig array{ + * fixturesMethod: string, + * cleanup: bool, + * ignoreCollidingDSN: bool, + * transaction: bool|null, + * entryScript: string, + * entryUrl: string, + * configFile: string, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null + * } + * @phpstan-type SessionBackup array{cookie: array, session: array, headers: array, clientContext: array{ cookieJar: CookieJar, history: History }} + * @phpstan-type ClientConfig array{ + * configFile: string, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null + * } */ class Yii2 extends Framework implements ActiveRecord, MultiSession, PartedModule { + /** + * @var list + */ + public array $loadedFixtures = []; + /** * Application config file must be set. + * @var ModuleConfig */ protected array $config = [ 'fixturesMethod' => '_fixtures', 'cleanup' => true, 'ignoreCollidingDSN' => false, 'transaction' => true, + 'configFile' => null, 'entryScript' => '', 'entryUrl' => 'http://localhost/index-test.php', 'responseCleanMethod' => Yii2Connector::CLEAN_CLEAR, @@ -191,13 +245,6 @@ class Yii2 extends Framework implements ActiveRecord, MultiSession, PartedModule 'applicationClass' => null, ]; - protected array $requiredFields = ['configFile']; - - /** - * @var Yii2Connector\FixturesStore[] - */ - public array $loadedFixtures = []; - /** * Helper to manage database connections */ @@ -209,7 +256,7 @@ class Yii2 extends Framework implements ActiveRecord, MultiSession, PartedModule private TransactionForcer $transactionForcer; /** - * @var array The contents of $_SERVER upon initialization of this object. + * @var array The contents of $_SERVER upon initialization of this object. * This is only used to restore it upon object destruction. * It MUST not be used anywhere else. */ @@ -247,6 +294,7 @@ protected function onReconfigure(): void { parent::onReconfigure(); $this->getClient()->resetApplication(); + $this->validateConfig(); $this->configureClient($this->config); $this->yiiLogger->getAndClearLog(); $this->getClient()->startApp($this->yiiLogger); @@ -272,9 +320,18 @@ private function initServerGlobal(): void ]); } + /** + * @phpstan-assert ValidConfig $this->config + */ protected function validateConfig(): void { parent::validateConfig(); + if (!isset($this->config['configFile'])) { + throw new ModuleConfigException( + self::class, + "The application config file was not configured" + ); + } $pathToConfig = codecept_absolute_path($this->config['configFile']); if (!is_file($pathToConfig)) { throw new ModuleConfigException( @@ -297,15 +354,20 @@ protected function validateConfig(): void } } - protected function configureClient(array $settings): void + + /** + * @param ClientConfig $settings + */ + private function configureClient(array $settings): void { - $settings['configFile'] = codecept_absolute_path($settings['configFile']); - foreach ($settings as $key => $value) { - if (property_exists($this->client, $key)) { - $this->getClient()->$key = $value; - } - } - $this->getClient()->resetApplication(); + $client = $this->getClient(); + $client->configFile = codecept_absolute_path($settings['configFile']); + $client->responseCleanMethod = $settings['responseCleanMethod']; + $client->requestCleanMethod = $settings['requestCleanMethod']; + $client->recreateApplication = $settings['recreateApplication']; + $client->closeSessionOnRecreateApplication = $settings['closeSessionOnRecreateApplication']; + $client->applicationClass = $settings['applicationClass']; + $client->resetApplication(); } /** @@ -324,6 +386,7 @@ protected function recreateClient(): void 'SERVER_PORT' => $parsedUrl['port'] ?? '80', 'HTTPS' => isset($parsedUrl['scheme']) && $parsedUrl['scheme'] === 'https' ]); + $this->validateConfig(); $this->configureClient($this->config); } @@ -355,11 +418,11 @@ private function loadFixtures(object $test): void { $this->debugSection('Fixtures', 'Loading fixtures'); if ($this->loadedFixtures === [] - && method_exists($test, $this->_getConfig('fixturesMethod')) + && method_exists($test, $this->config['fixturesMethod']) ) { $connectionWatcher = new ConnectionWatcher(); $connectionWatcher->start(); - $this->haveFixtures(call_user_func([$test, $this->_getConfig('fixturesMethod')])); + $this->haveFixtures($test->{$this->config['fixturesMethod']}()); $connectionWatcher->stop(); $connectionWatcher->closeAll(); } @@ -395,6 +458,9 @@ public function _after(TestInterface $test): void parent::_after($test); } + /** + * @param \Exception $fail + */ public function _failed(TestInterface $test, $fail): void { $log = $this->yiiLogger->getAndClearLog(); @@ -422,6 +488,9 @@ protected function rollbackTransactions(): void } } + /** + * @return list + */ public function _parts(): array { return ['orm', 'init', 'fixtures', 'email', 'route']; @@ -486,10 +555,10 @@ public function amLoggedInAs(int|string|IdentityInterface $user): void * ``` * instead of calling `haveFixtures` in Cest `_before` * - * @param $fixtures * @part fixtures + * @param array $fixtures */ - public function haveFixtures($fixtures): void + public function haveFixtures(array $fixtures): void { if (empty($fixtures)) { return; @@ -505,17 +574,17 @@ public function haveFixtures($fixtures): void * Array of fixture instances * * @part fixtures + * @return array */ - public function grabFixtures() + public function grabFixtures(): array { - if ($this->loadedFixtures === []) { - return []; + $result = []; + foreach($this->loadedFixtures as $store) { + foreach($store->getFixtures() as $name => $fixture) { + $result[$name] = $fixture; + } } - - return array_merge(...array_map( - fn($fixturesStore) => $fixturesStore->getFixtures(), - $this->loadedFixtures - )); + return $result; } /** @@ -534,12 +603,10 @@ public function grabFixtures() * $user = $I->grabFixture('users', 'user1'); * ``` * - * @param $name - * @return mixed * @throws \Codeception\Exception\ModuleException if the fixture is not found * @part fixtures */ - public function grabFixture($name, $index = null) + public function grabFixture(string $name, null|string $index = null): Fixture|\yii\db\ActiveRecord|null { $fixtures = $this->grabFixtures(); if (!isset($fixtures[$name])) { @@ -627,9 +694,10 @@ public function dontSeeRecord(string $model, array $attributes = []): void * * @param class-string<\yii\db\ActiveRecord> $model * @param array $attributes + * @return ActiveRecordInterface|null|array * @part orm */ - public function grabRecord(string $model, array $attributes = []): \yii\db\ActiveRecord|null|array + public function grabRecord(string $model, array $attributes = []): ActiveRecordInterface|null|array { return $this->findRecord($model, $attributes); } @@ -637,8 +705,9 @@ public function grabRecord(string $model, array $attributes = []): \yii\db\Activ /** * @param class-string<\yii\db\ActiveRecord> $model Class name * @param array $attributes + * @return ActiveRecordInterface|null|array */ - protected function findRecord(string $model, array $attributes = []): \yii\db\ActiveRecord|null|array + protected function findRecord(string $model, array $attributes = []): ActiveRecordInterface|null|array { if (!class_exists($model)) { throw new RuntimeException("Class $model does not exist"); @@ -668,12 +737,12 @@ protected function findRecord(string $model, array $attributes = []): \yii\db\Ac * ``` * * @param string $route A route - * @param array $params Additional route parameters + * @param array $params Additional route parameters * @part route */ public function amOnRoute(string $route, array $params = []): void { - if (Yii::$app->controller === null) { + if (Yii::$app?->controller === null) { $route = "/{$route}"; } @@ -681,27 +750,6 @@ public function amOnRoute(string $route, array $params = []): void $this->amOnPage(Url::to($params)); } - /** - * Gets a component from the Yii container. Throws an exception if the - * component is not available - * - * ```php - * grabComponent('mailer'); - * ``` - * - * @throws \Codeception\Exception\ModuleException - * @deprecated in your tests you can use \Yii::$app directly. - */ - public function grabComponent(string $component): null|object - { - try { - return $this->getClient()->getComponent($component); - } catch (ConfigurationException $e) { - throw new ModuleException($this, $e->getMessage()); - } - } - /** * Checks that an email is sent. * @@ -749,6 +797,7 @@ public function dontSeeEmailIsSent(): void * ``` * * @part email + * @return list List of sent emails * @throws \Codeception\Exception\ModuleException */ public function grabSentEmails(): array @@ -771,15 +820,17 @@ public function grabSentEmails(): array * ``` * @part email */ - public function grabLastSentEmail(): object + public function grabLastSentEmail(): BaseMessage|null { $this->seeEmailIsSent(); $messages = $this->grabSentEmails(); - return end($messages); + + return $messages[array_key_last($messages)] ?? null; } /** * Returns a list of regex patterns for recognized domain names + * @return non-empty-list */ public function getInternalDomains(): array { @@ -797,9 +848,9 @@ private function defineConstants(): void * Sets a cookie and, if validation is enabled, signs it. * @param string $name The name of the cookie * @param string $val The value of the cookie - * @param array $params Additional cookie params like `domain`, `path`, `expires` and `secure`. + * @param array{domain?: string, path?: string, expires?: int, secure?:bool} $params Additional cookie params like `domain`, `path`, `expires` and `secure`. */ - public function setCookie($name, $val, $params = []) + public function setCookie($name, $val, $params = []): void { parent::setCookie($name, $this->getClient()->hashCookieData($name, $val), $params); } @@ -838,16 +889,17 @@ public function _initializeSession(): void /** * Return the session content for future restoring. Implements MultiSession. - * @return array backup data + * @return SessionBackup */ public function _backupSession(): array { - if (Yii::$app instanceof Application && Yii::$app->has('session', true) && Yii::$app->session->useCustomStorage) { + if (Yii::$app instanceof WebApplication && Yii::$app->has('session', true) && Yii::$app->session->useCustomStorage) { throw new ModuleException($this, "Yii2 MultiSession only supports the default session backend."); } return [ 'clientContext' => $this->getClient()->getContext(), 'headers' => $this->headers, + // @phpstan-ignore nullCoalesce.variable 'cookie' => $_COOKIE ?? [], 'session' => $_SESSION ?? [], ]; @@ -855,7 +907,7 @@ public function _backupSession(): array /** * Restore a session. Implements MultiSession. - * @param array $session output of _backupSession() + * @param SessionBackup $session output of _backupSession() */ public function _loadSession($session): void { @@ -872,6 +924,7 @@ public function _loadSession($session): void /** * Close and dump a session. Implements MultiSession. + * @param mixed $session */ public function _closeSession($session = null): void { diff --git a/tests/cases/sqlite/functional/SqLiteCest.php b/tests/cases/sqlite/functional/SqLiteCest.php index 1670ee0..ea3b4c7 100644 --- a/tests/cases/sqlite/functional/SqLiteCest.php +++ b/tests/cases/sqlite/functional/SqLiteCest.php @@ -25,15 +25,15 @@ public function _fixtures() public function testSharedPDO(FunctionalTester $I) { /** @var Connection $db1 */ - $db1 = $I->grabComponent('db1'); + $db1 = \Yii::$app->get('db1'); $I->assertSame(['test'], $db1->schema->getTableNames('', true)); /** @var Connection $db21 */ - $db21 = $I->grabComponent('db21'); + $db21 = \Yii::$app->get('db21'); $I->assertSame(['test'], $db21->schema->getTableNames('', true)); /** @var Connection $db22 */ - $db22 = $I->grabComponent('db22'); + $db22 = \Yii::$app->get('db22'); $I->assertSame(['test'], $db22->schema->getTableNames('', true)); } @@ -41,7 +41,7 @@ public function testSharedPDO(FunctionalTester $I) public function testTransaction(FunctionalTester $I) { /** @var Connection $db1 */ - $db1 = $I->grabComponent('db1'); + $db1 = \Yii::$app->get('db1'); $I->assertFalse($db1->isActive); $db1->open(); $I->assertNotNull($db1->getTransaction());