5
5
namespace Codeception \Lib \Connector ;
6
6
7
7
use Codeception \Exception \ConfigurationException ;
8
+ use Codeception \Exception \ModuleConfigException ;
8
9
use Codeception \Lib \Connector \Yii2 \Logger ;
9
10
use Codeception \Lib \Connector \Yii2 \TestMailer ;
10
11
use Codeception \Util \Debug ;
13
14
use Symfony \Component \BrowserKit \Cookie ;
14
15
use Symfony \Component \BrowserKit \CookieJar ;
15
16
use Symfony \Component \BrowserKit \History ;
17
+ use Symfony \Component \BrowserKit \Request as BrowserkitRequest ;
18
+ use yii \web \Request as YiiRequest ;
16
19
use Symfony \Component \BrowserKit \Response ;
17
20
use Yii ;
21
+ use yii \base \Component ;
22
+ use yii \base \Event ;
18
23
use yii \base \ExitException ;
19
24
use yii \base \Security ;
20
25
use yii \base \UserException ;
26
+ use yii \mail \BaseMessage ;
21
27
use yii \mail \MessageInterface ;
22
28
use yii \web \Application ;
23
29
use yii \web \ErrorHandler ;
26
32
use yii \web \Response as YiiResponse ;
27
33
use yii \web \User ;
28
34
35
+
36
+ /**
37
+ * @extends Client<BrowserkitRequest, Response>
38
+ */
29
39
class Yii2 extends Client
30
40
{
31
41
use Shared \PhpSuperGlobalsConverter;
@@ -98,18 +108,29 @@ class Yii2 extends Client
98
108
public string |null $ applicationClass = null ;
99
109
100
110
111
+ /**
112
+ * @var list<BaseMessage>
113
+ */
101
114
private array $ emails = [];
102
115
103
116
/**
104
- * @deprecated since 2.5, will become protected in 3.0. Directly access to \Yii::$app if you need to interact with it.
105
117
* @internal
106
118
*/
107
- public function getApplication (): \yii \base \Application
119
+ protected function getApplication (): \yii \base \Application
108
120
{
109
121
if (!isset (Yii::$ app )) {
110
122
$ this ->startApp ();
111
123
}
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 ;
113
134
}
114
135
115
136
public function resetApplication (bool $ closeSession = true ): void
@@ -120,9 +141,7 @@ public function resetApplication(bool $closeSession = true): void
120
141
}
121
142
Yii::$ app = null ;
122
143
\yii \web \UploadedFile::reset ();
123
- if (method_exists (\yii \base \Event::class, 'offAll ' )) {
124
- \yii \base \Event::offAll ();
125
- }
144
+ Event::offAll ();
126
145
Yii::setLogger (null );
127
146
// This resolves an issue with database connections not closing properly.
128
147
gc_collect_cycles ();
@@ -161,23 +180,23 @@ public function findAndLoginUser(int|string|IdentityInterface $user): void
161
180
* @param string $value The value of the cookie
162
181
* @return string The value to send to the browser
163
182
*/
164
- public function hashCookieData ($ name , $ value ): string
183
+ public function hashCookieData (string $ name , string $ value ): string
165
184
{
166
- $ app = $ this ->getApplication ();
167
- if (!$ app -> request ->enableCookieValidation ) {
185
+ $ request = $ this ->getWebRequest ();
186
+ if (!$ request ->enableCookieValidation ) {
168
187
return $ value ;
169
188
}
170
- return $ app -> security ->hashData (serialize ([$ name , $ value ]), $ app -> request ->cookieValidationKey );
189
+ return $ this -> getApplication ()-> security ->hashData (serialize ([$ name , $ value ]), $ request ->cookieValidationKey );
171
190
}
172
191
173
192
/**
174
193
* @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
176
195
*/
177
196
public function getInternalDomains (): array
178
197
{
179
- /** @var \yii\web\UrlManager $urlManager */
180
198
$ urlManager = $ this ->getApplication ()->urlManager ;
199
+
181
200
$ domains = [$ this ->getDomainRegex ($ urlManager ->hostInfo )];
182
201
if ($ urlManager ->enablePrettyUrl ) {
183
202
foreach ($ urlManager ->rules as $ rule ) {
@@ -187,12 +206,12 @@ public function getInternalDomains(): array
187
206
}
188
207
}
189
208
}
190
- return array_unique ($ domains );
209
+ return array_values ( array_unique ($ domains) );
191
210
}
192
211
193
212
/**
194
213
* @internal
195
- * @return array List of sent emails
214
+ * @return list<BaseMessage> List of sent emails
196
215
*/
197
216
public function getEmails (): array
198
217
{
@@ -211,13 +230,14 @@ public function clearEmails(): void
211
230
/**
212
231
* @internal
213
232
*/
214
- public function getComponent ($ name )
233
+ public function getComponent (string $ name ): object | null
215
234
{
216
235
$ app = $ this ->getApplication ();
217
- if (!$ app ->has ($ name )) {
236
+ $ result = $ app ->get ($ name , false );
237
+ if (!isset ($ result )) {
218
238
throw new ConfigurationException ("Component $ name is not available in current application " );
219
239
}
220
- return $ app -> get ( $ name ) ;
240
+ return $ result ;
221
241
}
222
242
223
243
/**
@@ -240,6 +260,9 @@ function ($matches) use (&$parameters): string {
240
260
$ template
241
261
);
242
262
}
263
+ if ($ template === null ) {
264
+ throw new \RuntimeException ("Failed to parse domain regex " );
265
+ }
243
266
$ template = preg_quote ($ template );
244
267
$ template = strtr ($ template , $ parameters );
245
268
return '/^ ' . $ template . '$/u ' ;
@@ -251,7 +274,7 @@ function ($matches) use (&$parameters): string {
251
274
*/
252
275
public function getCsrfParamName (): string
253
276
{
254
- return $ this ->getApplication ()-> request ->csrfParam ;
277
+ return $ this ->getWebRequest () ->csrfParam ;
255
278
}
256
279
257
280
public function startApp (?\yii \log \Logger $ logger = null ): void
@@ -268,7 +291,11 @@ public function startApp(?\yii\log\Logger $logger = null): void
268
291
}
269
292
270
293
$ 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 ;
272
299
273
300
if ($ logger instanceof \yii \log \Logger) {
274
301
Yii::setLogger ($ logger );
@@ -278,9 +305,9 @@ public function startApp(?\yii\log\Logger $logger = null): void
278
305
}
279
306
280
307
/**
281
- * @param \Symfony\Component\BrowserKit\Request $request
308
+ * @param BrowserkitRequest $request
282
309
*/
283
- public function doRequest (object $ request ): \ Symfony \ Component \ BrowserKit \ Response
310
+ public function doRequest (object $ request ): Response
284
311
{
285
312
$ _COOKIE = $ request ->getCookies ();
286
313
$ _SERVER = $ request ->getServer ();
@@ -337,9 +364,9 @@ public function doRequest(object $request): \Symfony\Component\BrowserKit\Respon
337
364
* Sending the response is problematic because it tries to send headers.
338
365
*/
339
366
$ app ->trigger ($ app ::EVENT_BEFORE_REQUEST );
340
- $ response = $ app ->handleRequest ($ yiiRequest );
367
+ $ yiiResponse = $ app ->handleRequest ($ yiiRequest );
341
368
$ app ->trigger ($ app ::EVENT_AFTER_REQUEST );
342
- $ response ->send ();
369
+ $ yiiResponse ->send ();
343
370
} catch (\Exception $ e ) {
344
371
if ($ e instanceof UserException) {
345
372
// 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
350
377
// for exceptions not related to Http, we pass them to Codeception
351
378
throw $ e ;
352
379
}
353
- $ response = $ app ->response ;
380
+ $ yiiResponse = $ app ->response ;
354
381
}
355
382
356
- $ this ->encodeCookies ($ response , $ yiiRequest , $ app ->security );
383
+ $ this ->encodeCookies ($ yiiResponse , $ yiiRequest , $ app ->security );
357
384
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 ));
360
387
}
361
388
362
389
$ 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 ' );
365
394
}
366
395
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 ());
374
397
}
375
398
376
-
377
399
/**
378
400
* Encodes the cookies and adds them to the headers.
379
401
* @throws \yii\base\InvalidConfigException
380
402
*/
381
403
protected function encodeCookies (
382
404
YiiResponse $ response ,
383
- Request $ request ,
405
+ YiiRequest $ request ,
384
406
Security $ security
385
407
): void {
386
408
if ($ request ->enableCookieValidation ) {
@@ -433,11 +455,19 @@ protected function mockMailer(array $config): array
433
455
434
456
$ mailerConfig = [
435
457
'class ' => TestMailer::class,
436
- 'callback ' => function (MessageInterface $ message ): void {
458
+ 'callback ' => function (BaseMessage $ message ): void {
437
459
$ this ->emails [] = $ message ;
438
460
}
439
461
];
440
462
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
+ }
441
471
if (isset ($ config ['components ' ]['mailer ' ]) && is_array ($ config ['components ' ]['mailer ' ])) {
442
472
foreach ($ config ['components ' ]['mailer ' ] as $ name => $ value ) {
443
473
if (in_array ($ name , $ allowedOptions , true )) {
@@ -487,7 +517,7 @@ public function setContext(array $context): void
487
517
*/
488
518
public function closeSession (): void
489
519
{
490
- $ app = \Yii:: $ app ;
520
+ $ app = $ this -> getApplication () ;
491
521
if ($ app instanceof \yii \web \Application && $ app ->has ('session ' , true )) {
492
522
$ app ->session ->close ();
493
523
}
@@ -511,8 +541,8 @@ protected function resetResponse(Application $app): void
511
541
Debug::debug (<<<TEXT
512
542
[WARNING] You are attaching event handlers or behaviors to the response object. But the Yii2 module is configured to recreate
513
543
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.
516
546
TEXT
517
547
);
518
548
$ method = self ::CLEAN_CLEAR ;
0 commit comments