Skip to content

Commit 5c2ea10

Browse files
SamMousasamdark
authored andcommitted
Implemented alternate strategies for clearing response / request objects (#4915)
* Implemented alternate strategies for clearing response / request objects * Fixed nitpick CS * Removed constant visibility, which is not supported in older PHP versions
1 parent 01e0ce6 commit 5c2ea10

File tree

2 files changed

+147
-4
lines changed

2 files changed

+147
-4
lines changed

src/Codeception/Lib/Connector/Yii2.php

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use Yii;
1111
use yii\base\ExitException;
1212
use yii\base\Security;
13+
use yii\web\Application;
14+
use yii\web\ErrorHandler;
1315
use yii\web\HttpException;
1416
use yii\web\Request;
1517
use yii\web\Response as YiiResponse;
@@ -18,11 +20,50 @@ class Yii2 extends Client
1820
{
1921
use Shared\PhpSuperGlobalsConverter;
2022

23+
const CLEAN_METHODS = [
24+
self::CLEAN_RECREATE,
25+
self::CLEAN_CLEAR,
26+
self::CLEAN_FORCE_RECREATE,
27+
self::CLEAN_MANUAL
28+
];
29+
/**
30+
* Clean the response object by recreating it.
31+
* This might lose behaviors / event handlers / other changes that are done in the application bootstrap phase.
32+
*/
33+
const CLEAN_RECREATE = 'recreate';
34+
/**
35+
* Same as recreate but will not warn when behaviors / event handlers are lost.
36+
*/
37+
const CLEAN_FORCE_RECREATE = 'force_recreate';
38+
/**
39+
* Clean the response object by resetting specific properties via its' `clear()` method.
40+
* This will keep behaviors / event handlers, but could inadvertently leave some changes intact.
41+
* @see \Yii\web\Response::clear()
42+
*/
43+
const CLEAN_CLEAR = 'clear';
44+
45+
/**
46+
* Do not clean the response, instead the test writer will be responsible for manually resetting the response in
47+
* between requests during one test
48+
*/
49+
const CLEAN_MANUAL = 'manual';
50+
51+
2152
/**
2253
* @var string application config file
2354
*/
2455
public $configFile;
2556

57+
/**
58+
* @var string method for cleaning the response object before each request
59+
*/
60+
public $responseCleanMethod;
61+
62+
/**
63+
* @var string method for cleaning the request object before each request
64+
*/
65+
public $requestCleanMethod;
66+
2667
/**
2768
* @return \yii\web\Application
2869
*/
@@ -99,7 +140,9 @@ public function doRequest($request)
99140
* @todo Implement some kind of check to see if someone tried to change the objects' properties and expects
100141
* those changes to be reflected in the reponse.
101142
*/
102-
$app->set('response', $app->getComponents()['response']);
143+
$this->resetResponse($app);
144+
145+
103146

104147
// disabling logging. Logs are slowing test execution down
105148
foreach ($app->log->targets as $target) {
@@ -114,7 +157,7 @@ public function doRequest($request)
114157
* @todo Implement some kind of check to see if someone tried to change the objects' properties and expects
115158
* those changes to be reflected in the reponse.
116159
*/
117-
$app->set('request', $app->getComponents()['request']);
160+
$this->resetRequest($app);
118161

119162
$yiiRequest = $app->getRequest();
120163
if ($request->getContent() !== null) {
@@ -134,7 +177,6 @@ public function doRequest($request)
134177
$app->trigger($app::EVENT_BEFORE_REQUEST);
135178
$response = $app->handleRequest($yiiRequest);
136179
$app->trigger($app::EVENT_AFTER_REQUEST);
137-
codecept_debug($response->isSent);
138180
$response->send();
139181
} catch (\Exception $e) {
140182
if ($e instanceof HttpException) {
@@ -259,4 +301,83 @@ public function restart()
259301
parent::restart();
260302
$this->resetApplication();
261303
}
304+
305+
/**
306+
* Resets the applications' response object.
307+
* The method used depends on the module configuration.
308+
*/
309+
protected function resetResponse(Application $app)
310+
{
311+
$method = $this->responseCleanMethod;
312+
// First check the current response object.
313+
if (($app->response->hasEventHandlers(\yii\web\Response::EVENT_BEFORE_SEND)
314+
|| $app->response->hasEventHandlers(\yii\web\Response::EVENT_AFTER_SEND)
315+
|| $app->response->hasEventHandlers(\yii\web\Response::EVENT_AFTER_PREPARE)
316+
|| count($app->response->getBehaviors()) > 0
317+
) && $method === self::CLEAN_RECREATE
318+
) {
319+
Debug::debug(<<<TEXT
320+
[WARNING] You are attaching event handlers or behaviors to the response object. But the Yii2 module is configured to recreate
321+
the response object, this means any behaviors or events that are not attached in the component config will be lost.
322+
We will fall back to clearing the response. If you are certain you want to recreate it, please configure
323+
responseCleanMethod = 'force_recreate' in the module.
324+
TEXT
325+
);
326+
$method = self::CLEAN_CLEAR;
327+
}
328+
329+
switch ($method) {
330+
case self::CLEAN_FORCE_RECREATE:
331+
case self::CLEAN_RECREATE:
332+
$app->set('response', $app->getComponents()['response']);
333+
break;
334+
case self::CLEAN_CLEAR:
335+
$app->response->clear();
336+
break;
337+
case self::CLEAN_MANUAL:
338+
break;
339+
}
340+
}
341+
342+
protected function resetRequest(Application $app)
343+
{
344+
$method = $this->requestCleanMethod;
345+
$request = $app->request;
346+
347+
// First check the current request object.
348+
if (count($request->getBehaviors()) > 0 && $method === self::CLEAN_RECREATE) {
349+
Debug::debug(<<<TEXT
350+
[WARNING] You are attaching event handlers or behaviors to the request object. But the Yii2 module is configured to recreate
351+
the request object, this means any behaviors or events that are not attached in the component config will be lost.
352+
We will fall back to clearing the request. If you are certain you want to recreate it, please configure
353+
requestCleanMethod = 'force_recreate' in the module.
354+
TEXT
355+
);
356+
$method = self::CLEAN_CLEAR;
357+
}
358+
359+
switch ($method) {
360+
case self::CLEAN_FORCE_RECREATE:
361+
case self::CLEAN_RECREATE:
362+
$app->set('request', $app->getComponents()['request']);
363+
break;
364+
case self::CLEAN_CLEAR:
365+
$request->getHeaders()->removeAll();
366+
$request->getCookies()->removeAll();
367+
$request->setBaseUrl(null);
368+
$request->setHostInfo(null);
369+
$request->setPathInfo(null);
370+
$request->setScriptFile(null);
371+
$request->setScriptUrl(null);
372+
$request->setUrl(null);
373+
$request->setPort(null);
374+
$request->setSecurePort(null);
375+
$request->setAcceptableContentTypes(null);
376+
$request->setAcceptableLanguages(null);
377+
378+
break;
379+
case self::CLEAN_MANUAL:
380+
break;
381+
}
382+
}
262383
}

src/Codeception/Module/Yii2.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
* * `cleanup` - (default: true) cleanup fixtures after the test
3838
* * `ignoreCollidingDSN` - (default: false) When 2 database connections use the same DSN but different settings an exception will be thrown, set this to true to disable this behavior.
3939
* * `fixturesMethod` - (default: _fixtures) Name of the method used for creating fixtures.
40-
*
40+
* * `responseCleanMethod` - (default: clear) Method for cleaning the response object. Note that this is only for multiple requests inside a single test case.
41+
* Between test casesthe whole application is always recreated
42+
* * `requestCleanMethod` - (default: clear) Method for cleaning the request object. Note that this is only for multiple requests inside a single test case.
43+
* Between test cases the whole application is always recreated
4144
* You can use this module by setting params in your functional.suite.yml:
4245
*
4346
* ```yaml
@@ -150,6 +153,8 @@ class Yii2 extends Framework implements ActiveRecord, PartedModule
150153
'transaction' => null,
151154
'entryScript' => '',
152155
'entryUrl' => 'http://localhost/index-test.php',
156+
'responseCleanMethod' => Yii2Connector::CLEAN_CLEAR,
157+
'requestCleanMethod' => Yii2Connector::CLEAN_RECREATE
153158
];
154159

155160
protected $requiredFields = ['configFile'];
@@ -178,6 +183,7 @@ class Yii2 extends Framework implements ActiveRecord, PartedModule
178183
* It MUST not be used anywhere else.
179184
*/
180185
private $server;
186+
181187
public function _initialize()
182188
{
183189
if ($this->config['transaction'] === null) {
@@ -232,6 +238,20 @@ protected function validateConfig()
232238
"The application config file does not exist: " . Configuration::projectDir() . $this->config['configFile']
233239
);
234240
}
241+
242+
if (!in_array($this->config['responseCleanMethod'], Yii2Connector::CLEAN_METHODS)) {
243+
throw new ModuleConfigException(
244+
__CLASS__,
245+
"The response clean method must be one of: " . implode(", ", Yii2Connector::CLEAN_METHODS)
246+
);
247+
}
248+
249+
if (!in_array($this->config['requestCleanMethod'], Yii2Connector::CLEAN_METHODS)) {
250+
throw new ModuleConfigException(
251+
__CLASS__,
252+
"The response clean method must be one of: " . implode(", ", Yii2Connector::CLEAN_METHODS)
253+
);
254+
}
235255
}
236256

237257

@@ -250,6 +270,7 @@ public function _before(TestInterface $test)
250270
]);
251271

252272
$this->client->configFile = Configuration::projectDir() . $this->config['configFile'];
273+
$this->client->responseCleanMethod = $this->config['responseCleanMethod'];
253274

254275
$this->client->resetApplication();
255276
$app = $this->client->getApplication();
@@ -352,6 +373,7 @@ protected function startTransactions()
352373
&& $this->dsnCache[$connection->dsn] !== $key
353374
&& !$this->config['ignoreCollidingDSN']
354375
) {
376+
355377
$this->debugSection('WARNING', <<<TEXT
356378
You use multiple connections to the same DSN ({$connection->dsn}) with different configuration.
357379
These connections will not see the same database state since we cannot share a transaction between different PDO

0 commit comments

Comments
 (0)