@@ -167,22 +167,21 @@ class Yii2 extends Framework implements ActiveRecord, PartedModule
167
167
protected $ requiredFields = ['configFile ' ];
168
168
169
169
/**
170
- * @var array Array of Transaction objects indexed by a string key
171
- */
172
- private $ transactions = [];
173
- /**
174
- * @var \PDO[] Array of PDO objects indexed by a string key
170
+ * @var Yii2Connector\FixturesStore[]
175
171
*/
176
- private $ pdoCache = [];
172
+ public $ loadedFixtures = [];
173
+
177
174
/**
178
- * @var string[] Array of cache keys indexes by their DSN
175
+ * Helper to manage database connections
176
+ * @var Yii2Connector\ConnectionWatcher
179
177
*/
180
- private $ dsnCache = [] ;
178
+ private $ connectionWatcher ;
181
179
182
180
/**
183
- * @var Yii2Connector\FixturesStore[]
181
+ * Helper to force database transaction
182
+ * @var Yii2Connector\TransactionForcer
184
183
*/
185
- public $ loadedFixtures = [] ;
184
+ private $ transactionForcer ;
186
185
187
186
/**
188
187
* @var array The contents of $_SERVER upon initialization of this object.
@@ -299,13 +298,17 @@ public function _before(TestInterface $test)
299
298
$ this ->recreateClient ();
300
299
$ this ->client ->startApp ();
301
300
301
+ $ this ->connectionWatcher = new Yii2Connector \ConnectionWatcher ();
302
+ $ this ->connectionWatcher ->start ();
303
+
302
304
// load fixtures before db transaction
303
305
if ($ test instanceof \Codeception \Test \Cest) {
304
306
$ this ->loadFixtures ($ test ->getTestClass ());
305
307
} else {
306
308
$ this ->loadFixtures ($ test );
307
309
}
308
310
311
+
309
312
$ this ->startTransactions ();
310
313
}
311
314
@@ -317,24 +320,14 @@ public function _before(TestInterface $test)
317
320
private function loadFixtures ($ test )
318
321
{
319
322
$ this ->debugSection ('Fixtures ' , 'Loading fixtures ' );
320
- /** @var Connection[] $connections */
321
- $ connections = [];
322
- // Register event handler.
323
- Event::on (Connection::class, Connection::EVENT_AFTER_OPEN , function (Event $ event ) use (&$ connections ) {
324
- $ this ->debugSection ('Fixtures ' , 'Opened database connection: ' . $ event ->sender ->dsn );
325
- $ connections [] = $ event ->sender ;
326
- });
327
323
if (empty ($ this ->loadedFixtures )
328
324
&& method_exists ($ test , $ this ->_getConfig ('fixturesMethod ' ))
329
325
) {
326
+ $ connectionWatcher = new Yii2Connector \ConnectionWatcher ();
327
+ $ connectionWatcher ->start ();
330
328
$ this ->haveFixtures (call_user_func ([$ test , $ this ->_getConfig ('fixturesMethod ' )]));
331
- }
332
-
333
- Event::offAll ();
334
- // Close all connections so they get properly reopened after the transaction handler has been attached.
335
- foreach ($ connections as $ connection ) {
336
- $ this ->debugSection ('Fixtures ' , 'Closing database connection: ' . $ connection ->dsn );
337
- $ connection ->close ();
329
+ $ connectionWatcher ->stop ();
330
+ $ connectionWatcher ->closeAll ();
338
331
}
339
332
$ this ->debugSection ('Fixtures ' , 'Done ' );
340
333
}
@@ -350,6 +343,10 @@ public function _after(TestInterface $test)
350
343
351
344
$ this ->rollbackTransactions ();
352
345
346
+ $ this ->connectionWatcher ->stop ();
347
+ $ this ->connectionWatcher ->closeAll ();
348
+ unset($ this ->connectionWatcher );
349
+
353
350
if ($ this ->config ['cleanup ' ]) {
354
351
foreach ($ this ->loadedFixtures as $ fixture ) {
355
352
$ fixture ->unloadFixtures ();
@@ -365,80 +362,21 @@ public function _after(TestInterface $test)
365
362
parent ::_after ($ test );
366
363
}
367
364
368
- public function connectionOpenHandler (Event $ event )
369
- {
370
- if ($ event ->sender instanceof Connection) {
371
- $ connection = $ event ->sender ;
372
- /*
373
- * We should check if the known PDO objects are the same, in which case we should reuse the PDO
374
- * object so only 1 transaction is started and multiple connections to the same database see the
375
- * same data (due to writes inside a transaction not being visible from the outside).
376
- *
377
- */
378
- $ key = md5 (json_encode ([
379
- 'dsn ' => $ connection ->dsn ,
380
- 'user ' => $ connection ->username ,
381
- 'pass ' => $ connection ->password ,
382
- 'attributes ' => $ connection ->attributes ,
383
- 'emulatePrepare ' => $ connection ->emulatePrepare ,
384
- 'charset ' => $ connection ->charset
385
- ]));
386
-
387
- /*
388
- * If keys match we assume connections are "similar enough".
389
- */
390
- if (isset ($ this ->pdoCache [$ key ])) {
391
- $ connection ->pdo = $ this ->pdoCache [$ key ];
392
- } else {
393
- $ this ->pdoCache [$ key ] = $ connection ->pdo ;
394
- }
395
-
396
- if (isset ($ this ->dsnCache [$ connection ->dsn ])
397
- && $ this ->dsnCache [$ connection ->dsn ] !== $ key
398
- && !$ this ->config ['ignoreCollidingDSN ' ]
399
- ) {
400
- $ this ->debugSection ('WARNING ' , <<<TEXT
401
- You use multiple connections to the same DSN ( {$ connection ->dsn }) with different configuration.
402
- These connections will not see the same database state since we cannot share a transaction between different PDO
403
- instances.
404
- You can remove this message by adding 'ignoreCollidingDSN = true' in the module configuration.
405
- TEXT
406
- );
407
- Debug::pause ();
408
- }
409
-
410
- if (isset ($ this ->transactions [$ key ])) {
411
- $ this ->debugSection ('Database ' , 'Reusing PDO, so no need for a new transaction ' );
412
- return ;
413
- }
414
-
415
- $ this ->debugSection ('Database ' , 'Transaction started for: ' . $ connection ->dsn );
416
- $ this ->transactions [$ key ] = $ connection ->beginTransaction ();
417
- }
418
-
419
- }
420
-
421
365
protected function startTransactions ()
422
366
{
423
367
if ($ this ->config ['transaction ' ]) {
424
- // This should register handlers that start a transaction whenever a connection opens and add it to the transactions array.
425
- $ this ->debug ('Transaction ' , 'Registering connection event handler ' );
426
- Event::on (Connection::class, Connection::EVENT_AFTER_OPEN , [$ this , 'connectionOpenHandler ' ]);
368
+ $ this ->transactionForcer = new Yii2Connector \TransactionForcer ($ this ->config ['ignoreCollidingDSN ' ]);
369
+ $ this ->transactionForcer ->start ();
427
370
}
428
371
}
429
372
430
373
protected function rollbackTransactions ()
431
374
{
432
- $ this ->debugSection ('Transaction ' , 'Rolling back ' . count ($ this ->transactions ) . ' transactions ' );
433
- Event::off (Connection::class, Connection::EVENT_AFTER_OPEN , [$ this , 'connectionOpenHandler ' ]);
434
- /** @var Transaction $transaction */
435
- foreach ($ this ->transactions as $ transaction ) {
436
- $ transaction ->rollBack ();
437
- $ this ->debugSection ('Database ' , 'Transaction cancelled; all changes reverted. ' );
375
+ if (isset ($ this ->transactionForcer )) {
376
+ $ this ->transactionForcer ->rollbackAll ();
377
+ $ this ->transactionForcer ->stop ();
378
+ unset($ this ->transactionForcer );
438
379
}
439
- $ this ->transactions = [];
440
- $ this ->pdoCache = [];
441
- $ this ->dsnCache = [];
442
380
}
443
381
444
382
public function _parts ()
0 commit comments