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 ;
16
18
use Symfony \Component \BrowserKit \Response ;
17
19
use Yii ;
20
+ use yii \base \Component ;
21
+ use yii \base \Event ;
18
22
use yii \base \ExitException ;
19
23
use yii \base \Security ;
20
24
use yii \base \UserException ;
25
+ use yii \mail \BaseMessage ;
21
26
use yii \mail \MessageInterface ;
22
27
use yii \web \Application ;
23
28
use yii \web \ErrorHandler ;
26
31
use yii \web \Response as YiiResponse ;
27
32
use yii \web \User ;
28
33
34
+
35
+ /**
36
+ * @extends Client<BrowserkitRequest, Response>
37
+ */
29
38
class Yii2 extends Client
30
39
{
31
40
use Shared \PhpSuperGlobalsConverter;
@@ -98,18 +107,29 @@ class Yii2 extends Client
98
107
public string |null $ applicationClass = null ;
99
108
100
109
110
+ /**
111
+ * @var list<BaseMessage>
112
+ */
101
113
private array $ emails = [];
102
114
103
115
/**
104
- * @deprecated since 2.5, will become protected in 3.0. Directly access to \Yii::$app if you need to interact with it.
105
116
* @internal
106
117
*/
107
- public function getApplication (): \yii \base \Application
118
+ protected function getApplication (): \yii \base \Application
108
119
{
109
120
if (!isset (Yii::$ app )) {
110
121
$ this ->startApp ();
111
122
}
112
- return Yii::$ app ;
123
+ return Yii::$ app ?? throw new \RuntimeException ('Failed to create Yii2 application ' );
124
+ }
125
+
126
+ private function getWebRequest (): Request
127
+ {
128
+ $ request = $ this ->getApplication ()->request ;
129
+ if (!$ request instanceof Request) {
130
+ throw new \RuntimeException ('Request component is not of type ' . Request::class);
131
+ }
132
+ return $ request ;
113
133
}
114
134
115
135
public function resetApplication (bool $ closeSession = true ): void
@@ -120,9 +140,7 @@ public function resetApplication(bool $closeSession = true): void
120
140
}
121
141
Yii::$ app = null ;
122
142
\yii \web \UploadedFile::reset ();
123
- if (method_exists (\yii \base \Event::class, 'offAll ' )) {
124
- \yii \base \Event::offAll ();
125
- }
143
+ Event::offAll ();
126
144
Yii::setLogger (null );
127
145
// This resolves an issue with database connections not closing properly.
128
146
gc_collect_cycles ();
@@ -161,23 +179,27 @@ public function findAndLoginUser(int|string|IdentityInterface $user): void
161
179
* @param string $value The value of the cookie
162
180
* @return string The value to send to the browser
163
181
*/
164
- public function hashCookieData ($ name , $ value ): string
182
+ public function hashCookieData (string $ name , string $ value ): string
165
183
{
166
184
$ app = $ this ->getApplication ();
167
- if (!$ app ->request ->enableCookieValidation ) {
185
+ $ request = $ app ->getRequest ();
186
+ if (!$ request instanceof Request) {
187
+ throw new \RuntimeException ("Can't do cookie operations on non-web requests " );
188
+ }
189
+ if (!$ request ->enableCookieValidation ) {
168
190
return $ value ;
169
191
}
170
- return $ app ->security ->hashData (serialize ([$ name , $ value ]), $ app -> request ->cookieValidationKey );
192
+ return $ app ->security ->hashData (serialize ([$ name , $ value ]), $ request ->cookieValidationKey );
171
193
}
172
194
173
195
/**
174
196
* @internal
175
- * @return array List of regex patterns for recognized domain names
197
+ * @return non-empty-list<string> List of regex patterns for recognized domain names
176
198
*/
177
199
public function getInternalDomains (): array
178
200
{
179
- /** @var \yii\web\UrlManager $urlManager */
180
201
$ urlManager = $ this ->getApplication ()->urlManager ;
202
+
181
203
$ domains = [$ this ->getDomainRegex ($ urlManager ->hostInfo )];
182
204
if ($ urlManager ->enablePrettyUrl ) {
183
205
foreach ($ urlManager ->rules as $ rule ) {
@@ -187,12 +209,12 @@ public function getInternalDomains(): array
187
209
}
188
210
}
189
211
}
190
- return array_unique ($ domains );
212
+ return array_values ( array_unique ($ domains) );
191
213
}
192
214
193
215
/**
194
216
* @internal
195
- * @return array List of sent emails
217
+ * @return list<BaseMessage> List of sent emails
196
218
*/
197
219
public function getEmails (): array
198
220
{
@@ -211,13 +233,14 @@ public function clearEmails(): void
211
233
/**
212
234
* @internal
213
235
*/
214
- public function getComponent ($ name )
236
+ public function getComponent (string $ name ): object | null
215
237
{
216
238
$ app = $ this ->getApplication ();
217
- if (!$ app ->has ($ name )) {
239
+ $ result = $ app ->get ($ name , false );
240
+ if (!isset ($ result )) {
218
241
throw new ConfigurationException ("Component $ name is not available in current application " );
219
242
}
220
- return $ app -> get ( $ name ) ;
243
+ return $ result ;
221
244
}
222
245
223
246
/**
@@ -240,6 +263,9 @@ function ($matches) use (&$parameters): string {
240
263
$ template
241
264
);
242
265
}
266
+ if ($ template === null ) {
267
+ throw new \RuntimeException ("Failed to parse domain regex " );
268
+ }
243
269
$ template = preg_quote ($ template );
244
270
$ template = strtr ($ template , $ parameters );
245
271
return '/^ ' . $ template . '$/u ' ;
@@ -251,7 +277,7 @@ function ($matches) use (&$parameters): string {
251
277
*/
252
278
public function getCsrfParamName (): string
253
279
{
254
- return $ this ->getApplication ()-> request ->csrfParam ;
280
+ return $ this ->getWebRequest () ->csrfParam ;
255
281
}
256
282
257
283
public function startApp (?\yii \log \Logger $ logger = null ): void
@@ -268,7 +294,11 @@ public function startApp(?\yii\log\Logger $logger = null): void
268
294
}
269
295
270
296
$ config = $ this ->mockMailer ($ config );
271
- Yii::$ app = Yii::createObject ($ config );
297
+ $ app = Yii::createObject ($ config );
298
+ if (!$ app instanceof \yii \base \Application) {
299
+ throw new ModuleConfigException ($ this , "Failed to initialize Yii2 app " );
300
+ }
301
+ \Yii::$ app = $ app ;
272
302
273
303
if ($ logger instanceof \yii \log \Logger) {
274
304
Yii::setLogger ($ logger );
@@ -278,9 +308,9 @@ public function startApp(?\yii\log\Logger $logger = null): void
278
308
}
279
309
280
310
/**
281
- * @param \Symfony\Component\BrowserKit\Request $request
311
+ * @param BrowserkitRequest $request
282
312
*/
283
- public function doRequest (object $ request ): \ Symfony \ Component \ BrowserKit \ Response
313
+ public function doRequest (object $ request ): Response
284
314
{
285
315
$ _COOKIE = $ request ->getCookies ();
286
316
$ _SERVER = $ request ->getServer ();
@@ -362,18 +392,13 @@ public function doRequest(object $request): \Symfony\Component\BrowserKit\Respon
362
392
$ content = ob_get_clean ();
363
393
if (empty ($ content ) && !empty ($ response ->content ) && !isset ($ response ->stream )) {
364
394
throw new \Exception ('No content was sent from Yii application ' );
395
+ } elseif ($ content === false ) {
396
+ throw new \Exception ('Failed to get output buffer ' );
365
397
}
366
398
367
399
return new Response ($ content , $ response ->statusCode , $ response ->getHeaders ()->toArray ());
368
400
}
369
401
370
- protected function revertErrorHandler ()
371
- {
372
- $ handler = new ErrorHandler ();
373
- set_error_handler ([$ handler , 'errorHandler ' ]);
374
- }
375
-
376
-
377
402
/**
378
403
* Encodes the cookies and adds them to the headers.
379
404
* @throws \yii\base\InvalidConfigException
@@ -433,11 +458,19 @@ protected function mockMailer(array $config): array
433
458
434
459
$ mailerConfig = [
435
460
'class ' => TestMailer::class,
436
- 'callback ' => function (MessageInterface $ message ): void {
461
+ 'callback ' => function (BaseMessage $ message ): void {
437
462
$ this ->emails [] = $ message ;
438
463
}
439
464
];
440
465
466
+ if (isset ($ config ['components ' ])) {
467
+ if (!is_array ($ config ['components ' ])) {
468
+ throw new ModuleConfigException ($ this ,
469
+ "Yii2 config does not contain components key is not of type array " );
470
+ }
471
+ } else {
472
+ $ config ['components ' ] = [];
473
+ }
441
474
if (isset ($ config ['components ' ]['mailer ' ]) && is_array ($ config ['components ' ]['mailer ' ])) {
442
475
foreach ($ config ['components ' ]['mailer ' ] as $ name => $ value ) {
443
476
if (in_array ($ name , $ allowedOptions , true )) {
@@ -511,8 +544,8 @@ protected function resetResponse(Application $app): void
511
544
Debug::debug (<<<TEXT
512
545
[WARNING] You are attaching event handlers or behaviors to the response object. But the Yii2 module is configured to recreate
513
546
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.
547
+ We will fall back to clearing the response. If you are certain you want to recreate it, please configure
548
+ responseCleanMethod = 'force_recreate' in the module.
516
549
TEXT
517
550
);
518
551
$ method = self ::CLEAN_CLEAR ;
0 commit comments