Skip to content

feat: drop php8.1 php8.2 #121

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 8 commits into from
Feb 24, 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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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/"]
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ parameters:
dynamicConstantNames:
- CONSOLE
- YII_DEBUG
level: 5
level: 9
paths:
- src
checkMaybeUndefinedVariables: true
Expand Down
114 changes: 72 additions & 42 deletions src/Codeception/Lib/Connector/Yii2.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -26,6 +32,10 @@
use yii\web\Response as YiiResponse;
use yii\web\User;


/**
* @extends Client<BrowserkitRequest, Response>
*/
class Yii2 extends Client
{
use Shared\PhpSuperGlobalsConverter;
Expand Down Expand Up @@ -98,18 +108,29 @@ class Yii2 extends Client
public string|null $applicationClass = null;


/**
* @var list<BaseMessage>
*/
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
Expand All @@ -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();
Expand Down Expand Up @@ -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<string> 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) {
Expand All @@ -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<BaseMessage> List of sent emails
*/
public function getEmails(): array
{
Expand All @@ -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;
}

/**
Expand All @@ -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';
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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();
}
Expand All @@ -511,8 +541,8 @@ protected function resetResponse(Application $app): void
Debug::debug(<<<TEXT
[WARNING] You are attaching event handlers or behaviors to the response object. But the Yii2 module is configured to recreate
the response object, this means any behaviors or events that are not attached in the component config will be lost.
We will fall back to clearing the response. If you are certain you want to recreate it, please configure
responseCleanMethod = 'force_recreate' in the module.
We will fall back to clearing the response. If you are certain you want to recreate it, please configure
responseCleanMethod = 'force_recreate' in the module.
TEXT
);
$method = self::CLEAN_CLEAR;
Expand Down
6 changes: 5 additions & 1 deletion src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,15 @@ public function closeAll(): void
}
}

/**
* @param string|array<mixed>|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");
}
Expand Down
11 changes: 9 additions & 2 deletions src/Codeception/Lib/Connector/Yii2/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@

class Logger extends YiiLogger
{
/**
* @var \SplQueue<string>
*/
private \SplQueue $logQueue;

public function __construct(private int $maxLogItems = 5, array $config = [])
/**
* @param int $maxLogItems
* @param array<string, mixed> $config
*/
public function __construct(private readonly int $maxLogItems = 5, array $config = [])
{
parent::__construct($config);
$this->logQueue = new \SplQueue();
Expand All @@ -25,7 +32,7 @@ public function init(): void
}

/**
* @param string|array|YiiException $message
* @param string|array<mixed>|YiiException $message
* @param self::LEVEL_INFO|self::LEVEL_WARNING|self::LEVEL_ERROR $level
* @param string $category
*/
Expand Down
Loading