Skip to content

Commit 729e04d

Browse files
authored
Merge pull request #121 from Codeception/php8.3
feat: drop php8.1 php8.2
2 parents 49393a4 + 0e2ae2a commit 729e04d

File tree

9 files changed

+223
-120
lines changed

9 files changed

+223
-120
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88

99
strategy:
1010
matrix:
11-
php: [8.0, 8.1, 8.2, 8.3, 8.4]
11+
php: [8.3, 8.4]
1212

1313
steps:
1414
- name: Checkout code

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
],
1919
"minimum-stability": "RC",
2020
"require": {
21-
"php": "^8.0",
21+
"php": "^8.3",
2222
"codeception/codeception": "^5.0.8",
2323
"codeception/lib-innerbrowser": "^3.0 | ^4.0"
2424
},
@@ -29,8 +29,8 @@
2929
"codemix/yii2-localeurls": "^1.7",
3030
"codeception/module-asserts": ">= 3.0",
3131
"codeception/module-filesystem": "> 3.0",
32-
"phpstan/phpstan": "^1.10",
33-
"rector/rector": "^1.2"
32+
"phpstan/phpstan": "^2",
33+
"rector/rector": "^2"
3434
},
3535
"autoload":{
3636
"classmap": ["src/"]

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ parameters:
55
dynamicConstantNames:
66
- CONSOLE
77
- YII_DEBUG
8-
level: 5
8+
level: 9
99
paths:
1010
- src
1111
checkMaybeUndefinedVariables: true

src/Codeception/Lib/Connector/Yii2.php

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Codeception\Lib\Connector;
66

77
use Codeception\Exception\ConfigurationException;
8+
use Codeception\Exception\ModuleConfigException;
89
use Codeception\Lib\Connector\Yii2\Logger;
910
use Codeception\Lib\Connector\Yii2\TestMailer;
1011
use Codeception\Util\Debug;
@@ -13,11 +14,16 @@
1314
use Symfony\Component\BrowserKit\Cookie;
1415
use Symfony\Component\BrowserKit\CookieJar;
1516
use Symfony\Component\BrowserKit\History;
17+
use Symfony\Component\BrowserKit\Request as BrowserkitRequest;
18+
use yii\web\Request as YiiRequest;
1619
use Symfony\Component\BrowserKit\Response;
1720
use Yii;
21+
use yii\base\Component;
22+
use yii\base\Event;
1823
use yii\base\ExitException;
1924
use yii\base\Security;
2025
use yii\base\UserException;
26+
use yii\mail\BaseMessage;
2127
use yii\mail\MessageInterface;
2228
use yii\web\Application;
2329
use yii\web\ErrorHandler;
@@ -26,6 +32,10 @@
2632
use yii\web\Response as YiiResponse;
2733
use yii\web\User;
2834

35+
36+
/**
37+
* @extends Client<BrowserkitRequest, Response>
38+
*/
2939
class Yii2 extends Client
3040
{
3141
use Shared\PhpSuperGlobalsConverter;
@@ -98,18 +108,29 @@ class Yii2 extends Client
98108
public string|null $applicationClass = null;
99109

100110

111+
/**
112+
* @var list<BaseMessage>
113+
*/
101114
private array $emails = [];
102115

103116
/**
104-
* @deprecated since 2.5, will become protected in 3.0. Directly access to \Yii::$app if you need to interact with it.
105117
* @internal
106118
*/
107-
public function getApplication(): \yii\base\Application
119+
protected function getApplication(): \yii\base\Application
108120
{
109121
if (!isset(Yii::$app)) {
110122
$this->startApp();
111123
}
112-
return Yii::$app;
124+
return Yii::$app ?? throw new \RuntimeException('Failed to create Yii2 application');
125+
}
126+
127+
private function getWebRequest(): YiiRequest
128+
{
129+
$request = $this->getApplication()->request;
130+
if (!$request instanceof YiiRequest) {
131+
throw new \RuntimeException('Request component is not of type ' . YiiRequest::class);
132+
}
133+
return $request;
113134
}
114135

115136
public function resetApplication(bool $closeSession = true): void
@@ -120,9 +141,7 @@ public function resetApplication(bool $closeSession = true): void
120141
}
121142
Yii::$app = null;
122143
\yii\web\UploadedFile::reset();
123-
if (method_exists(\yii\base\Event::class, 'offAll')) {
124-
\yii\base\Event::offAll();
125-
}
144+
Event::offAll();
126145
Yii::setLogger(null);
127146
// This resolves an issue with database connections not closing properly.
128147
gc_collect_cycles();
@@ -161,23 +180,23 @@ public function findAndLoginUser(int|string|IdentityInterface $user): void
161180
* @param string $value The value of the cookie
162181
* @return string The value to send to the browser
163182
*/
164-
public function hashCookieData($name, $value): string
183+
public function hashCookieData(string $name, string $value): string
165184
{
166-
$app = $this->getApplication();
167-
if (!$app->request->enableCookieValidation) {
185+
$request = $this->getWebRequest();
186+
if (!$request->enableCookieValidation) {
168187
return $value;
169188
}
170-
return $app->security->hashData(serialize([$name, $value]), $app->request->cookieValidationKey);
189+
return $this->getApplication()->security->hashData(serialize([$name, $value]), $request->cookieValidationKey);
171190
}
172191

173192
/**
174193
* @internal
175-
* @return array List of regex patterns for recognized domain names
194+
* @return non-empty-list<string> List of regex patterns for recognized domain names
176195
*/
177196
public function getInternalDomains(): array
178197
{
179-
/** @var \yii\web\UrlManager $urlManager */
180198
$urlManager = $this->getApplication()->urlManager;
199+
181200
$domains = [$this->getDomainRegex($urlManager->hostInfo)];
182201
if ($urlManager->enablePrettyUrl) {
183202
foreach ($urlManager->rules as $rule) {
@@ -187,12 +206,12 @@ public function getInternalDomains(): array
187206
}
188207
}
189208
}
190-
return array_unique($domains);
209+
return array_values(array_unique($domains));
191210
}
192211

193212
/**
194213
* @internal
195-
* @return array List of sent emails
214+
* @return list<BaseMessage> List of sent emails
196215
*/
197216
public function getEmails(): array
198217
{
@@ -211,13 +230,14 @@ public function clearEmails(): void
211230
/**
212231
* @internal
213232
*/
214-
public function getComponent($name)
233+
public function getComponent(string $name): object|null
215234
{
216235
$app = $this->getApplication();
217-
if (!$app->has($name)) {
236+
$result = $app->get($name, false);
237+
if (!isset($result)) {
218238
throw new ConfigurationException("Component $name is not available in current application");
219239
}
220-
return $app->get($name);
240+
return $result;
221241
}
222242

223243
/**
@@ -240,6 +260,9 @@ function ($matches) use (&$parameters): string {
240260
$template
241261
);
242262
}
263+
if ($template === null) {
264+
throw new \RuntimeException("Failed to parse domain regex");
265+
}
243266
$template = preg_quote($template);
244267
$template = strtr($template, $parameters);
245268
return '/^' . $template . '$/u';
@@ -251,7 +274,7 @@ function ($matches) use (&$parameters): string {
251274
*/
252275
public function getCsrfParamName(): string
253276
{
254-
return $this->getApplication()->request->csrfParam;
277+
return $this->getWebRequest()->csrfParam;
255278
}
256279

257280
public function startApp(?\yii\log\Logger $logger = null): void
@@ -268,7 +291,11 @@ public function startApp(?\yii\log\Logger $logger = null): void
268291
}
269292

270293
$config = $this->mockMailer($config);
271-
Yii::$app = Yii::createObject($config);
294+
$app = Yii::createObject($config);
295+
if (!$app instanceof \yii\base\Application) {
296+
throw new ModuleConfigException($this, "Failed to initialize Yii2 app");
297+
}
298+
\Yii::$app = $app;
272299

273300
if ($logger instanceof \yii\log\Logger) {
274301
Yii::setLogger($logger);
@@ -278,9 +305,9 @@ public function startApp(?\yii\log\Logger $logger = null): void
278305
}
279306

280307
/**
281-
* @param \Symfony\Component\BrowserKit\Request $request
308+
* @param BrowserkitRequest $request
282309
*/
283-
public function doRequest(object $request): \Symfony\Component\BrowserKit\Response
310+
public function doRequest(object $request): Response
284311
{
285312
$_COOKIE = $request->getCookies();
286313
$_SERVER = $request->getServer();
@@ -337,9 +364,9 @@ public function doRequest(object $request): \Symfony\Component\BrowserKit\Respon
337364
* Sending the response is problematic because it tries to send headers.
338365
*/
339366
$app->trigger($app::EVENT_BEFORE_REQUEST);
340-
$response = $app->handleRequest($yiiRequest);
367+
$yiiResponse = $app->handleRequest($yiiRequest);
341368
$app->trigger($app::EVENT_AFTER_REQUEST);
342-
$response->send();
369+
$yiiResponse->send();
343370
} catch (\Exception $e) {
344371
if ($e instanceof UserException) {
345372
// 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
350377
// for exceptions not related to Http, we pass them to Codeception
351378
throw $e;
352379
}
353-
$response = $app->response;
380+
$yiiResponse = $app->response;
354381
}
355382

356-
$this->encodeCookies($response, $yiiRequest, $app->security);
383+
$this->encodeCookies($yiiResponse, $yiiRequest, $app->security);
357384

358-
if ($response->isRedirection) {
359-
Debug::debug("[Redirect with headers]" . print_r($response->getHeaders()->toArray(), true));
385+
if ($yiiResponse->isRedirection) {
386+
Debug::debug("[Redirect with headers]" . print_r($yiiResponse->getHeaders()->toArray(), true));
360387
}
361388

362389
$content = ob_get_clean();
363-
if (empty($content) && !empty($response->content) && !isset($response->stream)) {
364-
throw new \Exception('No content was sent from Yii application');
390+
if (empty($content) && !empty($yiiResponse->content) && !isset($yiiResponse->stream)) {
391+
throw new \RuntimeException('No content was sent from Yii application');
392+
} elseif ($content === false) {
393+
throw new \RuntimeException('Failed to get output buffer');
365394
}
366395

367-
return new Response($content, $response->statusCode, $response->getHeaders()->toArray());
368-
}
369-
370-
protected function revertErrorHandler()
371-
{
372-
$handler = new ErrorHandler();
373-
set_error_handler([$handler, 'errorHandler']);
396+
return new Response($content, $yiiResponse->statusCode, $yiiResponse->getHeaders()->toArray());
374397
}
375398

376-
377399
/**
378400
* Encodes the cookies and adds them to the headers.
379401
* @throws \yii\base\InvalidConfigException
380402
*/
381403
protected function encodeCookies(
382404
YiiResponse $response,
383-
Request $request,
405+
YiiRequest $request,
384406
Security $security
385407
): void {
386408
if ($request->enableCookieValidation) {
@@ -433,11 +455,19 @@ protected function mockMailer(array $config): array
433455

434456
$mailerConfig = [
435457
'class' => TestMailer::class,
436-
'callback' => function (MessageInterface $message): void {
458+
'callback' => function (BaseMessage $message): void {
437459
$this->emails[] = $message;
438460
}
439461
];
440462

463+
if (isset($config['components'])) {
464+
if (!is_array($config['components'])) {
465+
throw new ModuleConfigException($this,
466+
"Yii2 config does not contain components key is not of type array");
467+
}
468+
} else {
469+
$config['components'] = [];
470+
}
441471
if (isset($config['components']['mailer']) && is_array($config['components']['mailer'])) {
442472
foreach ($config['components']['mailer'] as $name => $value) {
443473
if (in_array($name, $allowedOptions, true)) {
@@ -487,7 +517,7 @@ public function setContext(array $context): void
487517
*/
488518
public function closeSession(): void
489519
{
490-
$app = \Yii::$app;
520+
$app = $this->getApplication();
491521
if ($app instanceof \yii\web\Application && $app->has('session', true)) {
492522
$app->session->close();
493523
}
@@ -511,8 +541,8 @@ protected function resetResponse(Application $app): void
511541
Debug::debug(<<<TEXT
512542
[WARNING] You are attaching event handlers or behaviors to the response object. But the Yii2 module is configured to recreate
513543
the response object, this means any behaviors or events that are not attached in the component config will be lost.
514-
We will fall back to clearing the response. If you are certain you want to recreate it, please configure
515-
responseCleanMethod = 'force_recreate' in the module.
544+
We will fall back to clearing the response. If you are certain you want to recreate it, please configure
545+
responseCleanMethod = 'force_recreate' in the module.
516546
TEXT
517547
);
518548
$method = self::CLEAN_CLEAR;

src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,15 @@ public function closeAll(): void
5858
}
5959
}
6060

61+
/**
62+
* @param string|array<mixed>|JsonSerializable $message
63+
* @return void
64+
*/
6165
protected function debug(string|array|JsonSerializable $message): void
6266
{
6367
$title = (new ReflectionClass($this))->getShortName();
6468
if (is_array($message) || is_object($message)) {
65-
$message = stripslashes(json_encode($message));
69+
$message = stripslashes(json_encode($message, JSON_THROW_ON_ERROR));
6670
}
6771
codecept_debug("[$title] $message");
6872
}

src/Codeception/Lib/Connector/Yii2/Logger.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111

1212
class Logger extends YiiLogger
1313
{
14+
/**
15+
* @var \SplQueue<string>
16+
*/
1417
private \SplQueue $logQueue;
1518

16-
public function __construct(private int $maxLogItems = 5, array $config = [])
19+
/**
20+
* @param int $maxLogItems
21+
* @param array<string, mixed> $config
22+
*/
23+
public function __construct(private readonly int $maxLogItems = 5, array $config = [])
1724
{
1825
parent::__construct($config);
1926
$this->logQueue = new \SplQueue();
@@ -25,7 +32,7 @@ public function init(): void
2532
}
2633

2734
/**
28-
* @param string|array|YiiException $message
35+
* @param string|array<mixed>|YiiException $message
2936
* @param self::LEVEL_INFO|self::LEVEL_WARNING|self::LEVEL_ERROR $level
3037
* @param string $category
3138
*/

0 commit comments

Comments
 (0)